commit 07f68645f6a8afc79ab81418ad6a740d7403ad0e Author: ritoseo Date: Fri Jul 25 12:19:29 2025 +0900 Initial Commit v2.0.13 with MQTT Library adding. diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..a5f05cd --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..837e304 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..464608e --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,155 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.2" + + signingConfigs { + debug { + storeFile file('D:\\Android\\rito3399.jks') + storePassword 'ritoseo' + keyAlias = 'ritokey' + keyPassword 'ritoseo' +// storePassword 'android' +// keyAlias = 'platform' +// keyPassword 'android' + } + + release { + storeFile file('D:\\Android\\rito3399.jks') + storePassword 'ritoseo' + keyAlias = 'ritokey' + keyPassword 'ritoseo' + } + } + + + defaultConfig { + applicationId "kr.co.rito.osicmanager" + minSdkVersion 26 + targetSdkVersion 30 + versionCode 1 + // For 119 Version + // versionName "1.0.18" + // For OSIC10 + // 2.0.2 - for YELLOW, BLUE KEY active mode working. + // 2.0.3 - for active mode hsact:// working + // 2.0.4 - for encoder live audio drop retry + // 2.0.5 - for 액티브 모드에서 방송 대기화면 표출 나오는 이슈, 액티브 모드에서 스트림 재시도 후 종료되는 이슈 처리 + // 2.0.6 - for VFD 설정 변경 시 LongPress : 컨펌, ShortPress : 취소 + // 2.0.7 - for 웹뷰 로딩 에러시 대기이미지 표시, 스트리밍 문제 발생시 "신호가 약합니다." 표시, stbpage/osic10.php 변경. runChannelList -> lastPlayUrl 설정 추가 + // 2.0.8 - for ShouldStopPlay, ShouldPlayUrl 추가, 웹뷰 로딩 대기이미지 패시브 모드 출력 버그 수정 + // 2.0.9 - for 리모컨 정지 시 블랙화면 나오도록 수정 - 2024-06-20 + // 2.0.10 - for 플레이어 버퍼 로그 추가 - 2024-07-25 [ logmanager 연동 필요 ] + // 2.0.11 - for MQTT 추가 ebcast 연동 + // 2.0.12 - for emergency.wav 변경 + // 2.0.13 - for AudioFilePlayer에서 MediaPlayer를 ExoPlayer로 변경. mEbcastMqttIp 없을 때 MQTT 실행 방지 + versionName "2.0.13" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + //ndk.abiFilters 'armeabi-v7a', 'arm64-v8a' + ndk.abiFilters 'arm64-v8a' + + externalNativeBuild { + cmake { + cppFlags "" + } + } + } + + buildTypes { + debug { + //buildConfigField "String", "SPECIAL_APP_MODE", "\"119NOTI_DEMO\"" + buildConfigField "String", "SPECIAL_APP_MODE", "\"\"" + } + + release { + //buildConfigField "String", "SPECIAL_APP_MODE", "\"119NOTI_DEMO\"" + buildConfigField "String", "SPECIAL_APP_MODE", "\"\"" + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + version "3.10.2" + } + } + ndkVersion '21.0.6113669' + + + android.applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = "OsicManager_v${defaultConfig.versionName}.apk" + } + } + +} + +dependencies { + implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + //compileOnly files('libs/ritocls.jar') + implementation fileTree(dir: 'libs', include: ['*.jar']) + api fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(dir: 'libs', include: ['*.aar']) + //implementation 'org.videolan.android:libvlc-all:3.3.0-eap10' + //implementation 'org.videolan.android:libvlc-all:3.4.0-eap4' + implementation 'org.videolan.android:libvlc-all:3.4.5' + implementation 'com.google.android.exoplayer:exoplayer:2.16.0' + + // mqtt + implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' + //implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1' + + implementation 'com.squareup.okhttp3:okhttp:3.10.0' +// implementation 'com.mindorks.android:prdownloader:0.6.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} + + +//preBuild { +// doLast { +// //def imlFile = file(project.name + ".iml") +// def imlFile = file("..\\.idea\\modules\\app\\" + project.parent.name + ".app.iml") +// println 'Change ' + project.name + '.iml order' +// try { +// def parsedXml = (new XmlParser()).parse(imlFile) +// def jdkNode = parsedXml.component[1].orderEntry.find { it.'@type' == 'jdk' } +// parsedXml.component[1].remove(jdkNode) +// def sdkString = "Android API " + android.compileSdkVersion.substring("android-".length()) + " Platform" +// println 'what' + sdkString +// new Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK']) +// groovy.xml.XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile)) +// } catch (FileNotFoundException e) { +// // nop, iml not found +// println "no iml found" +// } +// } +//} + + +//preBuild { +// doLast { +// def imlFile = file(project.name + ".app.iml") +// println 'Change ' + project.name + '.iml order' +// try { +// def parsedXml = (new XmlParser()).parse(imlFile) +// def jdkNode = parsedXml.component[1].orderEntry.find { it.'@type' == 'jdk' } +// parsedXml.component[1].remove(jdkNode) +// def sdkString = "Android API " + android.compileSdkVersion.substring("android-".length()) + " Platform" +// println 'what' + sdkString +// new Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK']) +// groovy.xml.XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile)) +// } catch (FileNotFoundException e) { +// // nop, iml not found +// println "no iml found" +// } +// } +//} \ No newline at end of file diff --git a/app/libs/libvlc-armv8-3.5.2-eap1.aar_ b/app/libs/libvlc-armv8-3.5.2-eap1.aar_ new file mode 100644 index 0000000..1e53b3e Binary files /dev/null and b/app/libs/libvlc-armv8-3.5.2-eap1.aar_ differ diff --git a/app/libs/ritocls.jar b/app/libs/ritocls.jar new file mode 100644 index 0000000..974d3e9 Binary files /dev/null and b/app/libs/ritocls.jar differ diff --git a/app/libs/rplayer-lib-debug.aar b/app/libs/rplayer-lib-debug.aar new file mode 100644 index 0000000..d888675 Binary files /dev/null and b/app/libs/rplayer-lib-debug.aar differ diff --git a/app/libs/rplayer-lib-debug.aar_201130 b/app/libs/rplayer-lib-debug.aar_201130 new file mode 100644 index 0000000..e213c3a Binary files /dev/null and b/app/libs/rplayer-lib-debug.aar_201130 differ diff --git a/app/libs/rplayer-lib-debug.aar_211130 b/app/libs/rplayer-lib-debug.aar_211130 new file mode 100644 index 0000000..5ce5b34 Binary files /dev/null and b/app/libs/rplayer-lib-debug.aar_211130 differ diff --git a/app/src/androidTest/java/kr/co/rito/osicmanager/ExampleInstrumentedTest.java b/app/src/androidTest/java/kr/co/rito/osicmanager/ExampleInstrumentedTest.java new file mode 100644 index 0000000..711a8f7 --- /dev/null +++ b/app/src/androidTest/java/kr/co/rito/osicmanager/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package kr.co.rito.osicmanager; + +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 Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("kr.co.rito.osicmanager", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..757b100 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/blackground.png b/app/src/main/assets/blackground.png new file mode 100644 index 0000000..a869da0 Binary files /dev/null and b/app/src/main/assets/blackground.png differ diff --git a/app/src/main/assets/downchimes.wav b/app/src/main/assets/downchimes.wav new file mode 100644 index 0000000..c465e47 Binary files /dev/null and b/app/src/main/assets/downchimes.wav differ diff --git a/app/src/main/assets/emergency.wav b/app/src/main/assets/emergency.wav new file mode 100644 index 0000000..6c6b5cc Binary files /dev/null and b/app/src/main/assets/emergency.wav differ diff --git a/app/src/main/assets/emergency_old.wav b/app/src/main/assets/emergency_old.wav new file mode 100644 index 0000000..b9b83c6 Binary files /dev/null and b/app/src/main/assets/emergency_old.wav differ diff --git a/app/src/main/assets/fire.wav b/app/src/main/assets/fire.wav new file mode 100644 index 0000000..1b90df7 Binary files /dev/null and b/app/src/main/assets/fire.wav differ diff --git a/app/src/main/assets/fire_ori.wav b/app/src/main/assets/fire_ori.wav new file mode 100644 index 0000000..7f344ba Binary files /dev/null and b/app/src/main/assets/fire_ori.wav differ diff --git a/app/src/main/assets/general.wav b/app/src/main/assets/general.wav new file mode 100644 index 0000000..749a309 Binary files /dev/null and b/app/src/main/assets/general.wav differ diff --git a/app/src/main/assets/mute.wav b/app/src/main/assets/mute.wav new file mode 100644 index 0000000..6f60a33 Binary files /dev/null and b/app/src/main/assets/mute.wav differ diff --git a/app/src/main/assets/rescue.pk b/app/src/main/assets/rescue.pk new file mode 100644 index 0000000..927b4c7 Binary files /dev/null and b/app/src/main/assets/rescue.pk differ diff --git a/app/src/main/assets/rescue.wav b/app/src/main/assets/rescue.wav new file mode 100644 index 0000000..7553045 Binary files /dev/null and b/app/src/main/assets/rescue.wav differ diff --git a/app/src/main/assets/upchimes.wav b/app/src/main/assets/upchimes.wav new file mode 100644 index 0000000..c6015ab Binary files /dev/null and b/app/src/main/assets/upchimes.wav differ diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..48cad76 --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,44 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +add_library( # Sets the name of the library. + osic-lib + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + native-lib.cpp osic-manager.cpp sha1sum.cpp mixer.c pcm.c) + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +find_library( # Sets the name of the path variable. + log-lib + + # Specifies the name of the NDK library that + # you want CMake to locate. + log ) + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +target_link_libraries( # Specifies the target library. + osic-lib + + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) \ No newline at end of file diff --git a/app/src/main/cpp/mixer.c b/app/src/main/cpp/mixer.c new file mode 100644 index 0000000..2f88c9d --- /dev/null +++ b/app/src/main/cpp/mixer.c @@ -0,0 +1,628 @@ +/* mixer.c +** +** Copyright 2011, The Android Open Source Project +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +** DAMAGE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#define __force +#define __bitwise +#define __user +#include + +#include "tinyalsa/asoundlib.h" + +struct mixer_ctl { + struct mixer *mixer; + struct snd_ctl_elem_info *info; + char **ename; +}; + +struct mixer { + int fd; + struct snd_ctl_card_info card_info; + struct snd_ctl_elem_info *elem_info; + struct mixer_ctl *ctl; + unsigned int count; +}; + +void mixer_close(struct mixer *mixer) +{ + unsigned int n,m; + + if (!mixer) + return; + + if (mixer->fd >= 0) + close(mixer->fd); + + if (mixer->ctl) { + for (n = 0; n < mixer->count; n++) { + if (mixer->ctl[n].ename) { + unsigned int max = mixer->ctl[n].info->value.enumerated.items; + for (m = 0; m < max; m++) + free(mixer->ctl[n].ename[m]); + free(mixer->ctl[n].ename); + } + } + free(mixer->ctl); + } + + if (mixer->elem_info) + free(mixer->elem_info); + + free(mixer); + + /* TODO: verify frees */ +} + +struct mixer *mixer_open(unsigned int card) +{ + struct snd_ctl_elem_list elist; + struct snd_ctl_elem_info tmp; + struct snd_ctl_elem_id *eid = NULL; + struct mixer *mixer = NULL; + unsigned int n, m; + int fd; + char fn[256]; + + snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card); + fd = open(fn, O_RDWR); + if (fd < 0) + return 0; + + memset(&elist, 0, sizeof(elist)); + if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) + goto fail; + + mixer = calloc(1, sizeof(*mixer)); + if (!mixer) + goto fail; + + mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl)); + mixer->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info)); + if (!mixer->ctl || !mixer->elem_info) + goto fail; + + if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0) + goto fail; + + eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id)); + if (!eid) + goto fail; + + mixer->count = elist.count; + mixer->fd = fd; + elist.space = mixer->count; + elist.pids = eid; + if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) + goto fail; + + for (n = 0; n < mixer->count; n++) { + struct snd_ctl_elem_info *ei = mixer->elem_info + n; + ei->id.numid = eid[n].numid; + if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0) + goto fail; + mixer->ctl[n].info = ei; + mixer->ctl[n].mixer = mixer; + if (ei->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) { + char **enames = calloc(ei->value.enumerated.items, sizeof(char*)); + if (!enames) + goto fail; + mixer->ctl[n].ename = enames; + for (m = 0; m < ei->value.enumerated.items; m++) { + memset(&tmp, 0, sizeof(tmp)); + tmp.id.numid = ei->id.numid; + tmp.value.enumerated.item = m; + if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0) + goto fail; + enames[m] = strdup(tmp.value.enumerated.name); + if (!enames[m]) + goto fail; + } + } + } + + free(eid); + return mixer; + +fail: + /* TODO: verify frees in failure case */ + if (eid) + free(eid); + if (mixer) + mixer_close(mixer); + else if (fd >= 0) + close(fd); + return 0; +} + +const char *mixer_get_name(struct mixer *mixer) +{ + return (const char *)mixer->card_info.name; +} + +unsigned int mixer_get_num_ctls(struct mixer *mixer) +{ + if (!mixer) + return 0; + + return mixer->count; +} + +struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id) +{ + if (mixer && (id < mixer->count)) + return mixer->ctl + id; + + return NULL; +} + +struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name) +{ + unsigned int n; + + if (!mixer) + return NULL; + + for (n = 0; n < mixer->count; n++) + if (!strcmp(name, (char*) mixer->elem_info[n].id.name)) + return mixer->ctl + n; + + return NULL; +} + +void mixer_ctl_update(struct mixer_ctl *ctl) +{ + ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info); +} + +const char *mixer_ctl_get_name(struct mixer_ctl *ctl) +{ + if (!ctl) + return NULL; + + return (const char *)ctl->info->id.name; +} + +enum mixer_ctl_type mixer_ctl_get_type(struct mixer_ctl *ctl) +{ + if (!ctl) + return MIXER_CTL_TYPE_UNKNOWN; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return MIXER_CTL_TYPE_BOOL; + case SNDRV_CTL_ELEM_TYPE_INTEGER: return MIXER_CTL_TYPE_INT; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return MIXER_CTL_TYPE_ENUM; + case SNDRV_CTL_ELEM_TYPE_BYTES: return MIXER_CTL_TYPE_BYTE; + case SNDRV_CTL_ELEM_TYPE_IEC958: return MIXER_CTL_TYPE_IEC958; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: return MIXER_CTL_TYPE_INT64; + default: return MIXER_CTL_TYPE_UNKNOWN; + }; +} + +const char *mixer_ctl_get_type_string(struct mixer_ctl *ctl) +{ + if (!ctl) + return ""; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return "BOOL"; + case SNDRV_CTL_ELEM_TYPE_INTEGER: return "INT"; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return "ENUM"; + case SNDRV_CTL_ELEM_TYPE_BYTES: return "BYTE"; + case SNDRV_CTL_ELEM_TYPE_IEC958: return "IEC958"; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: return "INT64"; + default: return "Unknown"; + }; +} + +unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl) +{ + if (!ctl) + return 0; + + return ctl->info->count; +} + +static int percent_to_int(struct snd_ctl_elem_info *ei, int percent) +{ + int range; + + if (percent > 100) + percent = 100; + else if (percent < 0) + percent = 0; + + range = (ei->value.integer.max - ei->value.integer.min); + + return ei->value.integer.min + (range * percent) / 100; +} + +static int int_to_percent(struct snd_ctl_elem_info *ei, int value) +{ + int range = (ei->value.integer.max - ei->value.integer.min); + + if (range == 0) + return 0; + + return ((value - ei->value.integer.min) / range) * 100; +} + +int mixer_ctl_get_percent(struct mixer_ctl *ctl, unsigned int id) +{ + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) + return -EINVAL; + + return int_to_percent(ctl->info, mixer_ctl_get_value(ctl, id)); +} + +int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent) +{ + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) + return -EINVAL; + + return mixer_ctl_set_value(ctl, id, percent_to_int(ctl->info, percent)); +} + +int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id) +{ + struct snd_ctl_elem_value ev; + int ret; + + if (!ctl || (id >= ctl->info->count)) + return -EINVAL; + + memset(&ev, 0, sizeof(ev)); + ev.id.numid = ctl->info->id.numid; + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + if (ret < 0) + return ret; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + return !!ev.value.integer.value[id]; + + case SNDRV_CTL_ELEM_TYPE_INTEGER: + return ev.value.integer.value[id]; + + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + return ev.value.enumerated.item[id]; + + case SNDRV_CTL_ELEM_TYPE_BYTES: + return ev.value.bytes.data[id]; + + default: + return -EINVAL; + } + + return 0; +} + +int mixer_ctl_is_access_tlv_rw(struct mixer_ctl *ctl) +{ + return (ctl->info->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE); +} + +int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count) +{ + struct snd_ctl_elem_value ev; + int ret = 0; + size_t size; + void *source; + size_t total_count; + + if ((!ctl) || !count || !array) + return -EINVAL; + + total_count = ctl->info->count; + + if ((ctl->info->type == SNDRV_CTL_ELEM_TYPE_BYTES) && + mixer_ctl_is_access_tlv_rw(ctl)) { + /* Additional two words is for the TLV header */ + total_count += TLV_HEADER_SIZE; + } + + if (count > total_count) + return -EINVAL; + + memset(&ev, 0, sizeof(ev)); + ev.id.numid = ctl->info->id.numid; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + if (ret < 0) + return ret; + size = sizeof(ev.value.integer.value[0]); + source = ev.value.integer.value; + break; + + case SNDRV_CTL_ELEM_TYPE_BYTES: + /* check if this is new bytes TLV */ + if (mixer_ctl_is_access_tlv_rw(ctl)) { + struct snd_ctl_tlv *tlv; + int ret; + + if (count > SIZE_MAX - sizeof(*tlv)) + return -EINVAL; + tlv = calloc(1, sizeof(*tlv) + count); + if (!tlv) + return -ENOMEM; + tlv->numid = ctl->info->id.numid; + tlv->length = count; + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_READ, tlv); + + source = tlv->tlv; + memcpy(array, source, count); + + free(tlv); + + return ret; + } else { + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + if (ret < 0) + return ret; + size = sizeof(ev.value.bytes.data[0]); + source = ev.value.bytes.data; + break; + } + + case SNDRV_CTL_ELEM_TYPE_IEC958: + size = sizeof(ev.value.iec958); + source = &ev.value.iec958; + break; + + default: + return -EINVAL; + } + + memcpy(array, source, size * count); + + return 0; +} + +int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value) +{ + struct snd_ctl_elem_value ev; + int ret; + + if (!ctl || (id >= ctl->info->count)) + return -EINVAL; + + memset(&ev, 0, sizeof(ev)); + ev.id.numid = ctl->info->id.numid; + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev); + if (ret < 0) + return ret; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + ev.value.integer.value[id] = !!value; + break; + + case SNDRV_CTL_ELEM_TYPE_INTEGER: + ev.value.integer.value[id] = value; + break; + + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + ev.value.enumerated.item[id] = value; + break; + + case SNDRV_CTL_ELEM_TYPE_BYTES: + ev.value.bytes.data[id] = value; + break; + + default: + return -EINVAL; + } + + return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); +} + +int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) +{ + struct snd_ctl_elem_value ev; + size_t size; + void *dest; + size_t total_count; + + if ((!ctl) || !count || !array) + return -EINVAL; + + total_count = ctl->info->count; + + if ((ctl->info->type == SNDRV_CTL_ELEM_TYPE_BYTES) && + mixer_ctl_is_access_tlv_rw(ctl)) { + /* Additional two words is for the TLV header */ + total_count += TLV_HEADER_SIZE; + } + + if (count > total_count) + return -EINVAL; + + memset(&ev, 0, sizeof(ev)); + ev.id.numid = ctl->info->id.numid; + + switch (ctl->info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + size = sizeof(ev.value.integer.value[0]); + dest = ev.value.integer.value; + break; + + case SNDRV_CTL_ELEM_TYPE_BYTES: + /* check if this is new bytes TLV */ + if (mixer_ctl_is_access_tlv_rw(ctl)) { + struct snd_ctl_tlv *tlv; + int ret = 0; + if (count > SIZE_MAX - sizeof(*tlv)) + return -EINVAL; + tlv = calloc(1, sizeof(*tlv) + count); + if (!tlv) + return -ENOMEM; + tlv->numid = ctl->info->id.numid; + tlv->length = count; + memcpy(tlv->tlv, array, count); + + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_WRITE, tlv); + free(tlv); + + return ret; + } else { + size = sizeof(ev.value.bytes.data[0]); + dest = ev.value.bytes.data; + } + break; + + case SNDRV_CTL_ELEM_TYPE_IEC958: + size = sizeof(ev.value.iec958); + dest = &ev.value.iec958; + break; + + default: + return -EINVAL; + } + + memcpy(dest, array, size * count); + + return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); +} + +int mixer_ctl_get_range_min(struct mixer_ctl *ctl) +{ + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) + return -EINVAL; + + return ctl->info->value.integer.min; +} + +int mixer_ctl_get_range_max(struct mixer_ctl *ctl) +{ + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER)) + return -EINVAL; + + return ctl->info->value.integer.max; +} + +unsigned int mixer_ctl_get_num_enums(struct mixer_ctl *ctl) +{ + if (!ctl) + return 0; + + return ctl->info->value.enumerated.items; +} + +const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl, + unsigned int enum_id) +{ + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) || + (enum_id >= ctl->info->value.enumerated.items)) + return NULL; + + return (const char *)ctl->ename[enum_id]; +} + +int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string) +{ + unsigned int i, num_enums; + struct snd_ctl_elem_value ev; + int ret; + + if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED)) + return -EINVAL; + + num_enums = ctl->info->value.enumerated.items; + for (i = 0; i < num_enums; i++) { + if (!strcmp(string, ctl->ename[i])) { + memset(&ev, 0, sizeof(ev)); + ev.value.enumerated.item[0] = i; + ev.id.numid = ctl->info->id.numid; + ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev); + if (ret < 0) + return ret; + return 0; + } + } + + return -EINVAL; +} + +/** Subscribes for the mixer events. + * @param mixer A mixer handle. + * @param subscribe value indicating subscribe or unsubscribe for events + * @returns On success, zero. + * On failure, non-zero. + * @ingroup libtinyalsa-mixer + */ +int mixer_subscribe_events(struct mixer *mixer, int subscribe) +{ + if (ioctl(mixer->fd, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) { + return -1; + } + return 0; +} + +/** Wait for mixer events. + * @param mixer A mixer handle. + * @param timeout timout value + * @returns On success, 1. + * On failure, -errno. + * On timeout, 0 + * @ingroup libtinyalsa-mixer + */ +int mixer_wait_event(struct mixer *mixer, int timeout) +{ + struct pollfd pfd; + + pfd.fd = mixer->fd; + pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL; + + for (;;) { + int err; + err = poll(&pfd, 1, timeout); + if (err < 0) + return -errno; + if (!err) + return 0; + if (pfd.revents & (POLLERR | POLLNVAL)) + return -EIO; + if (pfd.revents & (POLLIN | POLLOUT)) + return 1; + } +} diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp new file mode 100644 index 0000000..5aa0284 --- /dev/null +++ b/app/src/main/cpp/native-lib.cpp @@ -0,0 +1,703 @@ +#include +#include +#include +#include +#include <__locale> +#include "osic-manager.h" +#include +#include +#include "sha1sum.h" +#include "tinyalsa/asoundlib.h" + +#define LOG_TAG "ManagerNative" +#define LOGUNK(...) __android_log_print(ANDROID_LOG_UNKNOWN,LOG_TAG,__VA_ARGS__) +#define LOGDEF(...) __android_log_print(ANDROID_LOG_DEFAULT,LOG_TAG,__VA_ARGS__) +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_FATAL_ERROR,LOG_TAG,__VA_ARGS__) +#define LOGS(...) __android_log_print(ANDROID_SILENT_ERROR,LOG_TAG,__VA_ARGS__) + +static JavaVM* g_jvm; +static jobject g_clazz; +static jmethodID g_javafn_NotifyInfo; + +#define ACQUIRE_JNIENV(vm, envptr) \ + { \ + int getEnvStat = vm->GetEnv((void **)&envptr, JNI_VERSION_1_6); \ + if (getEnvStat == JNI_EDETACHED) { \ + if (vm->AttachCurrentThread(&envptr, NULL) != 0) { \ + LOGE("Unable to acquire JNIENV"); \ + } \ + } else if (getEnvStat == JNI_OK) { \ + } \ + } + +#define RELEASE_JNIENV(vm, envptr) \ + { \ + int getEnvStat = vm->GetEnv((void **)&envptr, JNI_VERSION_1_6); \ + if (getEnvStat != JNI_EDETACHED) { \ + vm->DetachCurrentThread(); \ + } \ + } + +int callNotifyInfo(int code, char* strValue) { + if(g_javafn_NotifyInfo != NULL) { + JNIEnv *env; + ACQUIRE_JNIENV(g_jvm, env); + jstring jstrResult = env->NewStringUTF(strValue); + env->CallVoidMethod(g_clazz, g_javafn_NotifyInfo, code, jstrResult); + env->DeleteLocalRef(jstrResult); + RELEASE_JNIENV(g_jvm, env); + } + + return 0; +} + + +extern "C" JNIEXPORT jstring JNICALL +Java_kr_co_rito_osicmanager_MainActivity_stringFromJNI( + JNIEnv* env, + jobject /* this */) { + char strBuffer[512] = {0, }; + pthread_t tid; + + get_interface_info("eth0", SIOCGIFHWADDR, strBuffer); + pthread_create(&tid, NULL, osic_start_thread, NULL); + //std::string hello = "Hello from C++"; + std::string hello = strBuffer; + return env->NewStringUTF(hello.c_str()); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_kr_co_rito_osicmanager_MainActivity_getIPAddress( + JNIEnv* env, + jobject /* this */) { + char strBuffer[512] = {0, }; + get_interface_info("eth0", SIOCGIFADDR, strBuffer); + std::string result = strBuffer; + return env->NewStringUTF(result.c_str()); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_kr_co_rito_osicmanager_MainActivity_getNetmask( + JNIEnv* env, + jobject /* this */) { + char strBuffer[512] = {0, }; + get_interface_info("eth0", SIOCGIFNETMASK, strBuffer); + std::string result = strBuffer; + return env->NewStringUTF(result.c_str()); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_kr_co_rito_osicmanager_MainActivity_getGateway( + JNIEnv* env, + jobject /* this */) { + char strBuffer[512] = {0, }; + get_default_gw(strBuffer); + std::string result = strBuffer; + return env->NewStringUTF(result.c_str()); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_kr_co_rito_osicmanager_MainActivity_getServerAddress( + JNIEnv* env, + jobject /* this */) { + char strBuffer[512] = {0, }; + get_server_addr(strBuffer); + std::string result = strBuffer; + return env->NewStringUTF(result.c_str()); +} + +extern "C" JNIEXPORT jint JNICALL +Java_kr_co_rito_osicmanager_MainActivity_getServerPort( + JNIEnv* env, + jobject /* this */) { + return get_server_port(); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_kr_co_rito_osicmanager_MainActivity_getConfigValue( + JNIEnv* env, + jobject thiz, + jstring target) { + char strBuffer[512] = {0, }; + const char *pType = env->GetStringUTFChars(target, 0); + getConfigValue((char *)pType, strBuffer); + env->ReleaseStringUTFChars(target, pType); + std::string result = strBuffer; + return env->NewStringUTF(result.c_str()); +} + +extern "C" JNIEXPORT void JNICALL +Java_kr_co_rito_osicmanager_MainActivity_nativeInit( + JNIEnv* env, + jobject thiz, + jstring version) { + char strBuffer[512] = {0, }; + + const char *pVersion = env->GetStringUTFChars(version, 0); + setVersionString(pVersion); + env->ReleaseStringUTFChars(version, pVersion); + + jclass cls = env->GetObjectClass(thiz); + g_clazz = env->NewWeakGlobalRef(thiz); + g_javafn_NotifyInfo = env->GetMethodID(cls, "nativeNotify", "(ILjava/lang/String;)V"); + env->GetJavaVM(&g_jvm); + + pthread_t tid; + pthread_create(&tid, NULL, osic_start_thread, NULL); +} + +extern "C" JNIEXPORT void JNICALL +Java_kr_co_rito_osicmanager_MainActivity_terminateService( + JNIEnv* env, + jobject thiz) { + quit_osic9_mode(); +} + +extern "C" JNIEXPORT void JNICALL +Java_kr_co_rito_osicmanager_MainActivity_doSync( + JNIEnv* env, + jobject thiz) { + ::sync(); +} + +extern "C" JNIEXPORT void JNICALL +Java_kr_co_rito_osicmanager_MainActivity_setState( + JNIEnv* env, + jobject thiz, + jstring target) { + const char *pTarget = env->GetStringUTFChars(target, 0); + //setVersionString(pTarget); + setStateTarget((char *)pTarget); + env->ReleaseStringUTFChars(target, pTarget); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_kr_co_rito_osicmanager_SystemUtil_getIPAddress( + JNIEnv* env, + jclass /* this */) { + char strBuffer[512] = {0, }; + get_interface_info("eth0", SIOCGIFADDR, strBuffer); + std::string result = strBuffer; + return env->NewStringUTF(result.c_str()); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_kr_co_rito_osicmanager_SystemUtil_getConfigValue( + JNIEnv* env, + jclass thiz, + jstring target) { + char strBuffer[512] = {0, }; + const char *pType = env->GetStringUTFChars(target, 0); + getConfigValue((char *)pType, strBuffer); + env->ReleaseStringUTFChars(target, pType); + std::string result = strBuffer; + return env->NewStringUTF(result.c_str()); +} + +extern "C" JNIEXPORT void JNICALL +Java_kr_co_rito_osicmanager_SystemUtil_setConfigValue( + JNIEnv* env, + jclass thiz, + jstring target, + jstring value) { + char strBuffer[512] = {0, }; + const char *pType = env->GetStringUTFChars(target, 0); + const char *pValue = env->GetStringUTFChars(value, 0); + sprintf(strBuffer, "ritosysc CONFIG-WRITE=[%s]%s", pType, pValue); + env->ReleaseStringUTFChars(target, pType); + env->ReleaseStringUTFChars(value, pValue); + system(strBuffer); + sprintf(strBuffer, "ritosysc CONFIG-SAVE"); + system(strBuffer); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_kr_co_rito_osicmanager_MainActivity_getFileCksum( + JNIEnv* env, + jobject thiz, + jstring target) { + char strBuffer[512] = {0, }; + const char *pPath = env->GetStringUTFChars(target, 0); + rito_cksum((char *)pPath, strBuffer); + env->ReleaseStringUTFChars(target, pPath); + std::string result = strBuffer; + return env->NewStringUTF(result.c_str()); +} + +extern "C" JNIEXPORT void JNICALL +Java_kr_co_rito_osicmanager_MainActivity_sendSocketMessage( + JNIEnv* env, + jobject thiz, + jstring target) { + const char *pTarget = env->GetStringUTFChars(target, 0); + sendSocketMessage((char *)pTarget); + env->ReleaseStringUTFChars(target, pTarget); +} + + +#include +#include + + +#define GPIO_IN 0 +#define GPIO_OUT 1 + +#define GPIO_LOW 0 +#define GPIO_HIGH 1 + +static int GPIOExport(int pin) +{ +#define BUFFER_MAX 5 + char buffer[BUFFER_MAX]; + ssize_t bytes_written; + int fd; + + fd = open("/sys/class/gpio/export", O_WRONLY); + if (-1 == fd) { + LOGW("Failed to open export for writing!"); + return(-1); + } + + bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin); + write(fd, buffer, bytes_written); + close(fd); + + char target_order[256]; + sprintf(target_order, "ritosysc SHELL-ORDER=chmod 666 /sys/class/gpio/gpio%d/direction", pin); + system(target_order); + sprintf(target_order, "ritosysc SHELL-ORDER=chmod 666 /sys/class/gpio/gpio%d/value", pin); + system(target_order); + return(0); +} + +static int GPIOUnexport(int pin) +{ + char buffer[BUFFER_MAX]; + ssize_t bytes_written; + int fd; + + fd = open("/sys/class/gpio/unexport", O_WRONLY); + if (-1 == fd) { + LOGW("Failed to open unexport for writing!"); + return(-1); + } + + bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin); + write(fd, buffer, bytes_written); + close(fd); + return(0); +} + +static int GPIODirection(int pin, int dir) +{ + static const char s_directions_str[] = "in\0out"; + +#define DIRECTION_MAX 64 + char path[DIRECTION_MAX]; + int fd; + + snprintf(path, DIRECTION_MAX, "/sys/class/gpio/gpio%d/direction", pin); + fd = open(path, O_WRONLY); + if (-1 == fd) { + LOGW("Failed to open gpio direction for writing!"); + return(-1); + } + + if (-1 == write(fd, &s_directions_str[GPIO_IN == dir ? 0 : 3], GPIO_IN == dir ? 2 : 3)) { + LOGW("Failed to set direction!"); + return(-1); + } + + close(fd); + return(0); +} + +#define VALUE_MAX 64 +static int GPIORead(int pin) +{ + char path[VALUE_MAX]; + char value_str[3]; + int fd; + + snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin); + fd = open(path, O_RDONLY); + if (-1 == fd) { + LOGW("Failed to open gpio value for reading!"); + return(-1); + } + + if (-1 == read(fd, value_str, 3)) { + LOGW("Failed to read value!"); + return(-1); + } + + close(fd); + + return(atoi(value_str)); +} + +static int GPIOWrite(int pin, int value) +{ + static const char s_values_str[] = "01"; + + char path[VALUE_MAX]; + int fd; + + snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin); + fd = open(path, O_WRONLY); + if (-1 == fd) { + LOGW("Failed to open gpio value for writing!\n"); + return(-1); + } + if (1 != write(fd, &s_values_str[GPIO_LOW == value ? 0 : 1], 1)) { + LOGW("Failed to write value!\n"); + return(-1); + } + + close(fd); + return(0); +} + +extern "C" JNIEXPORT jint JNICALL +Java_kr_co_rito_osicmanager_SystemUtil_getPinValue( + JNIEnv* env, + jclass /* this */, + jint pinNo) { + int ret; + ret = GPIORead(pinNo); + return ret; +} + +extern "C" JNIEXPORT jint JNICALL +Java_kr_co_rito_osicmanager_SystemUtil_setPinValue( + JNIEnv* env, + jclass /* this */, + jint pinNo, + jint value) { + int ret; + ret = GPIOWrite(pinNo, value); + return ret; +} + +extern "C" JNIEXPORT jint JNICALL +Java_kr_co_rito_osicmanager_SystemUtil_pinSetup( + JNIEnv* env, + jclass /* this */, + jint pinNo, + jint pinDirection) { + int ret; + + ret = GPIOExport(pinNo); + if(ret < 0) + return ret; + if(pinDirection == GPIO_IN) { + GPIODirection(pinNo, GPIO_OUT); + GPIOWrite(pinNo, GPIO_HIGH); + } else { + ret = GPIODirection(pinNo, pinDirection); + } + + + return ret; +} + + + +#define ID_RIFF 0x46464952 +#define ID_WAVE 0x45564157 +#define ID_FMT 0x20746d66 +#define ID_DATA 0x61746164 + +struct riff_wave_header { + uint32_t riff_id; + uint32_t riff_sz; + uint32_t wave_id; +}; + +struct chunk_header { + uint32_t id; + uint32_t sz; +}; + +struct chunk_fmt { + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; +}; + +int check_param(struct pcm_params *params, unsigned int param, unsigned int value, + char *param_name, char *param_unit) +{ + unsigned int min; + unsigned int max; + int is_within_bounds = 1; + + min = pcm_params_get_min(params, static_cast(param)); + if (value < min) { + fprintf(stderr, "%s is %u%s, device only supports >= %u%s\n", param_name, value, + param_unit, min, param_unit); + is_within_bounds = 0; + } + + max = pcm_params_get_max(params, static_cast(param)); + if (value > max) { + fprintf(stderr, "%s is %u%s, device only supports <= %u%s\n", param_name, value, + param_unit, max, param_unit); + is_within_bounds = 0; + } + + return is_within_bounds; +} + + +int sample_is_playable(unsigned int card, unsigned int device, unsigned int channels, + unsigned int rate, unsigned int bits, unsigned int period_size, + unsigned int period_count) +{ + struct pcm_params *params; + int can_play; + + params = pcm_params_get(card, device, PCM_OUT); + if (params == NULL) { + fprintf(stderr, "Unable to open PCM device %u.\n", device); + return 0; + } + + can_play = check_param(params, PCM_PARAM_RATE, rate, (char*)"Sample rate", (char*)"Hz"); + can_play &= check_param(params, PCM_PARAM_CHANNELS, channels, (char*)"Sample", (char*)" channels"); + can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, bits, (char*)"Bitrate", (char*)" bits"); + can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, period_size, (char*)"Period size", (char*)" frames"); + can_play &= check_param(params, PCM_PARAM_PERIODS, period_count, (char*)"Period count", (char*)" periods"); + + pcm_params_free(params); + + return can_play; +} + +void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels, + unsigned int rate, unsigned int bits, unsigned int period_size, + unsigned int period_count) +{ + struct pcm_config config; + struct pcm *pcm; + char *buffer; + int size; + int num_read; + + memset(&config, 0, sizeof(config)); + config.channels = channels; + config.rate = rate; + config.period_size = period_size; + config.period_count = period_count; + if (bits == 32) + config.format = PCM_FORMAT_S32_LE; + else if (bits == 24) + config.format = PCM_FORMAT_S24_3LE; + else if (bits == 16) + config.format = PCM_FORMAT_S16_LE; + config.start_threshold = 0; + config.stop_threshold = 0; + config.silence_threshold = 0; + + if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count)) { + return; + } + + pcm = pcm_open(card, device, PCM_OUT, &config); + if (!pcm || !pcm_is_ready(pcm)) { + fprintf(stderr, "Unable to open PCM device %u (%s)\n", + device, pcm_get_error(pcm)); + return; + } + + size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); + buffer = (char *)malloc(size); + if (!buffer) { + fprintf(stderr, "Unable to allocate %d bytes\n", size); + free(buffer); + pcm_close(pcm); + return; + } + + printf("Playing sample: %u ch, %u hz, %u bit\n", channels, rate, bits); + + do { + num_read = fread(buffer, 1, size, file); + if (num_read > 0) { + if (pcm_write(pcm, buffer, num_read)) { + fprintf(stderr, "Error playing sample\n"); + break; + } + } + } while (num_read > 0); + + free(buffer); + pcm_close(pcm); +} + +int do_tinyplay(char *filename, unsigned int device_id, unsigned int card_id) +{ + FILE *file; + struct riff_wave_header riff_wave_header; + struct chunk_header chunk_header; + struct chunk_fmt chunk_fmt; + unsigned int device = 0; + unsigned int card = 0; + unsigned int period_size = 1024; + unsigned int period_count = 4; + int more_chunks = 1; + + LOGW("RITO TRACE[%d]", __LINE__); + file = fopen(filename, "rb"); + if (!file) { + LOGW("Unable to open file '%s'\n", filename); + return 1; + } + + LOGW("RITO TRACE[%d]", __LINE__); + fread(&riff_wave_header, sizeof(riff_wave_header), 1, file); + if ((riff_wave_header.riff_id != ID_RIFF) || + (riff_wave_header.wave_id != ID_WAVE)) { + LOGW("Error: '%s' is not a riff/wave file\n", filename); + fclose(file); + return 1; + } + + LOGW("RITO TRACE[%d]", __LINE__); + do { + fread(&chunk_header, sizeof(chunk_header), 1, file); + + switch (chunk_header.id) { + case ID_FMT: + fread(&chunk_fmt, sizeof(chunk_fmt), 1, file); + /* If the format header is larger, skip the rest */ + if (chunk_header.sz > sizeof(chunk_fmt)) + fseek(file, chunk_header.sz - sizeof(chunk_fmt), SEEK_CUR); + break; + case ID_DATA: + /* Stop looking for chunks */ + more_chunks = 0; + break; + default: + /* Unknown chunk, skip bytes */ + fseek(file, chunk_header.sz, SEEK_CUR); + } + } while (more_chunks); + + LOGW("RITO TRACE[%d]", __LINE__); + device = device_id; + card = card_id; + + play_sample(file, card, device, chunk_fmt.num_channels, chunk_fmt.sample_rate, + chunk_fmt.bits_per_sample, period_size, period_count); + + LOGW("RITO TRACE[%d]", __LINE__); + fclose(file); + + return 0; +} + + +static struct pcm *gp_pcm = NULL; +int aout_setup(unsigned int card, unsigned int device, unsigned int channels, + unsigned int rate, unsigned int bits, unsigned int period_size, + unsigned int period_count) +{ + struct pcm_config config; + struct pcm *pcm; + char *buffer; + int size; + int num_read; + + memset(&config, 0, sizeof(config)); + config.channels = channels; + config.rate = rate; + config.period_size = period_size; + config.period_count = period_count; + if (bits == 32) + config.format = PCM_FORMAT_S32_LE; + else if (bits == 24) + config.format = PCM_FORMAT_S24_3LE; + else if (bits == 16) + config.format = PCM_FORMAT_S16_LE; + config.start_threshold = 0; + config.stop_threshold = 0; + config.silence_threshold = 0; + + if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count)) { + return -1; + } + + pcm = pcm_open(card, device, PCM_OUT, &config); + if (!pcm || !pcm_is_ready(pcm)) { + fprintf(stderr, "Unable to open PCM device %u (%s)\n", + device, pcm_get_error(pcm)); + return -2; + } + + size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); + buffer = (char *)malloc(size); + if (!buffer) { + fprintf(stderr, "Unable to allocate %d bytes\n", size); + free(buffer); + pcm_close(pcm); + return -3; + } + + printf("Playing sample: %u ch, %u hz, %u bit\n", channels, rate, bits); + + gp_pcm = pcm; + + return 0; +} + +void aout_close() { + if(gp_pcm) { + pcm_close(gp_pcm); + gp_pcm = NULL; + } +} + +extern "C" JNIEXPORT jint JNICALL +Java_kr_co_rito_osicmanager_SystemUtil_playWaveFile( + JNIEnv* env, + jclass /* this */, + jstring filePath, + jint device_id) { + int ret; + const char *pPath = env->GetStringUTFChars(filePath, 0); + ret = do_tinyplay((char*)pPath, device_id, 0); + env->ReleaseStringUTFChars(filePath, pPath); + return ret; +} + +extern "C" JNIEXPORT jint JNICALL +Java_kr_co_rito_osicmanager_SystemUtil_setupAudioOut( + JNIEnv* env, + jclass /* this */, + jint device_id) { + int ret = aout_setup(0, device_id, 2, 44100, 16, 1024, 4); + return ret; +} + +extern "C" JNIEXPORT jint JNICALL +Java_kr_co_rito_osicmanager_SystemUtil_writeAudioOut( + JNIEnv* env, + jclass /* this */, + jbyteArray buffer, + jint size) { + jbyte *pBuffer; + pBuffer = env->GetByteArrayElements(buffer, NULL); + pcm_write(gp_pcm, pBuffer, size); + env->ReleaseByteArrayElements(buffer, pBuffer, 0); + return size; +} + diff --git a/app/src/main/cpp/osic-manager.cpp b/app/src/main/cpp/osic-manager.cpp new file mode 100644 index 0000000..7c302cb --- /dev/null +++ b/app/src/main/cpp/osic-manager.cpp @@ -0,0 +1,2554 @@ +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "osic-manager.h" + +#define LOG_TAG "OsicManagerNative" +#define LOGUNK(...) __android_log_print(ANDROID_LOG_UNKNOWN,LOG_TAG,__VA_ARGS__) +#define LOGDEF(...) __android_log_print(ANDROID_LOG_DEFAULT,LOG_TAG,__VA_ARGS__) +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_FATAL_ERROR,LOG_TAG,__VA_ARGS__) +#define LOGS(...) __android_log_print(ANDROID_SILENT_ERROR,LOG_TAG,__VA_ARGS__) + +#define MAX_SUBTITLE_LENGTH 4096 +#define STRING_NOTIFY_COMMAND 10 +#define SERVER_PORT 10000 +#define BCAST_SERVER_PORT 37879 +#define MAX_STRING_LEN 512 +#define MAX_MSG_LENGTH 512 +// v3.2 for WE350o-015 +// v3.3 for WE350o-016 +// v3.4 for WE350o-017 +// v3.5 for WE350o-018 +// v3.6 for WE350o-019 +// v3.7 for WE350o-020 : Add Setting Speaker Volume +// v3.8 for WE350o-021 : Support Audio Streaming Play to the STB without USB DAC - 2016.04.17 +// v3.9 for WE350o-022 : Volume Table Patch - 2016.04.18 +// v4.0 for WE350o-023 : Bug Patch - 2016.04.19 +// v4.1 for WE350o-024 : asst/assm STC Drift - 2016.04.26 +// v4.2 for WE350o-026 : SPECIAL Version. Browser Support - 2017.04.12 +// v4.3 for WE350o-027 : Support TTS Chime Volume setting - 2017.02.15 +// v4.4 for WE350o-028 : Fix TTS Alarm volume issue - 2017.05.17 +// v4.5 for WE350o-029 : Fix Player status report by check decoder - 2017.06.10 +// v4.6 for WE350o-030 : Channel change button repeat duration longer - 2017.07.26 +// v4.7 for WE350o-031 : Support notification service - 2017.08.09 +// v4.8 for WE350o-032 : asst protocol retry - 2017.09.28 +// v4.9 for WE350o-034 : audio play after tts automatically in old version board - 2017.10.15 +// v5.0 for WE350o-035 : off the gpio_control as normal version +// v5.1 for WE350o-036 : change librmoutput.so previous version +// v5.2 for WE350o-037 : Fix Cast Image after audio broadcasting For Browser mode(NAJU) - 2018-03-03 +// v5.3 for WE350o-038 : Remove Cast Image for audio broadcasting For Browser mode(HWASOON) - 2018-08-22 +// v5.4 for WE350o-039 : Remove Cast Image for audio broadcasting For Browser mode(HWASOON) FIX - 2018-11-05 +// v5.5 for WE350o-040 : Support App Mode (Active/Passive) - 2018-12-22 +// v5.6 for WE350o-041 : For Active mode channel selection mode enhance[YeosuCity] - 2018-12-26 +// v5.7 for WE350o-042 : Fix RTSP High Bitrate Issue. Playable without audio data. - 2020-06-22 +#define VERSION_STRING "v5.7" +#define VOLUME_FILEPATH "/mnt/config/volume.ini" +#define SPEAKER_VOLUME_FILEPATH "/mnt/config/spkvolume.ini" + +/* manager에게 message 전송을 할 때에 사용되는 command */ +#define MANAGER_PLAYER_STATUS 1 +#define MANAGER_PLAYER_INFO 2 + +#define MANAGER_PLAYER_REQUEST 3 +#define MANAGER_DPLAYER_STATUS 4 +#define MANAGER_LISTENER_REQUEST 5 /* added by ritoseo - 2009. 6. 10 */ + +/* browser mode - normal, tv mode - added by ritoseo - 2010. 7. 19 */ +#define PLAYER_STATUS_TO_TV "status-to-tv" +#define PLAYER_STATUS_TO_NORMAL "status-to-normal" + +#define PLAYER_STATUS_CHANGE "status-change" +#define PLAYER_STATUS_SCAN "status-scan" /* added by ritoseo - 2010. 3. 26 */ +#define PLAYER_STATUS_IPTV_INFO "status-iptv-info" /* added by ritoseo - 2011. 1. 25 */ + +#define PLAYER_START_STATUS_DATA "start" +#define PLAYER_END_STATUS_DATA "end" +#define PLAYER_BUFFERING_START "buffering-start" +#define PLAYER_BUFFERING_END "buffering-end" +#define PLAYER_NETWORK_ERROR "network-error" +#define PLAYER_NETWORK_NOSIGNAL "network-nosignal" + +/* PLAYER_END_STATUS_DATA 의 value. 즉, 종료 status */ +#define PLAYER_END_STATUS_OK "ok" +#define PLAYER_END_STATUS_DRM_FAIL "drm-fail" +#define PLAYER_END_STATUS_NETWORK_FAIL "network-fail" +#define PLAYER_END_STATUS_STREAM_SERVER_FAIL "stream-server-fail" +#define PLAYER_END_STATUS_FILE_NOT_FOUND "file-not-found" +#define PLAYER_END_STATUS_USER_STOP "user-stop" +#define PLAYER_END_STATUS_DECODER_FAIL "decoder-fail" +#define PLAYER_END_STATUS_NO_STREAM_DATA "no-stream-data" + + +typedef enum { MESSAGE_ACK, MESSAGE_REPORT, MESSAGE_REGISTER, MESSAGE_HELLO } MESSAGE_TYPE; + +typedef struct _stbInfo { + char curService[MAX_STRING_LEN]; + char targetService[MAX_STRING_LEN]; + char audioService[MAX_STRING_LEN]; + char curState[MAX_STRING_LEN]; + char curAudioState[MAX_STRING_LEN]; + char targetState[MAX_STRING_LEN]; + char serverAddr[128]; + char forceMac[MAX_STRING_LEN]; + char unicastAddr[128]; + char terminalID[MAX_STRING_LEN]; + int serviceReportPeriod; + int stateReportPeriod; + int serverPort; + int unicastPort; + int unicastRecvPort; + int sock; + int cseq; + int shortTimeReportCount; + unsigned long long lastServiceRPT; // Last Service Report Time + unsigned long long lastStateRPT; // Last State Report Time + unsigned long long nextServiceRPT; // Next Service Report Time + unsigned long long nextStateRPT; // Next State Report Time + unsigned long long stateTargetTime; + unsigned long long serviceTargetTime; + unsigned long long lastSendHBT; // Last Send Heartbeat Time + + /* For Subtitle */ + int sub_x; + int sub_y; + int sub_width; + int sub_height; + int sub_speed; + int sub_count; + int sub_fcolor; + int sub_bcolor; + char sub_content[MAX_SUBTITLE_LENGTH]; + int sub_remainCount; + int sub_isStandby; + + int cec_on_order; + int cec_on_step; + unsigned long long cec_orderTime; + + /* For TTS Service */ + char ttsFileUrl[MAX_STRING_LEN]; + char ttsFileName[MAX_STRING_LEN]; + int ttsPlayCount; + int ttsVolume; + int chimeVolume; + int isOnTTSPlay; + int isOnASSPlay; + int isTTSOver; + int isTTSConcurrent; // is TTS over USB DAC? + + int bufferLevel; + + /* For NOTIFY Service */ + char notifyFileUrl[MAX_STRING_LEN]; + char notifyFileName[MAX_STRING_LEN]; + int isOnNotify; + + char notifyMsgType[64]; + char notifyMsgDatetime[64]; + char notifyMsgAddress[MAX_STRING_LEN]; + char notifyMsgDesc[MAX_SUBTITLE_LENGTH]; + char notifyMsgVehicle[MAX_STRING_LEN]; + + /* For NOTIFY version 2 */ + char notifyMsgAddressDoro[MAX_STRING_LEN]; + char notifyMsgAddressJibun[MAX_STRING_LEN]; + char notifyMsgDsrClass[MAX_STRING_LEN]; + char notifyMsgDsrSize[MAX_STRING_LEN]; + + char stbClassId[16]; + char eventType[MAX_STRING_LEN]; + char eventBaseUrl[MAX_STRING_LEN]; + char eventJsonRaw[MAX_SUBTITLE_LENGTH]; +} STB_INFO; + +STB_INFO g_stbInfo; +char g_versionString[32]; +struct timeval g_pivotTimeInfo; +int g_msgqid = -1; +int g_timeSyncDone; +int g_browserExist; +int g_audioCastNoUI; +int g_serviceAlive; +pthread_t g_tMsgRecv; +pthread_t g_tDac; +pthread_t g_tAudio; +pthread_t g_tBcast; +pthread_mutex_t g_mtxAudio; + +struct bit +{ + unsigned char hh:4; + unsigned char lh:4; +}; + +struct command_msg_st +{ + long iReceiver; + long iSender; + int iCommand; + int iLength; + char chData[MAX_MSG_LENGTH-16]; +}__attribute__((packed)); + +static int socketRecv(int sock, unsigned char *buffer, int maxSizeOfBuffer); + +static void system_safe(char *order) +{ + FILE *fp; + char dummy[1024]; + + fp = popen(order, "r"); + while(!feof(fp)) + fread(dummy, 1, sizeof(dummy), fp); + pclose(fp); +} + +static int getVolume() +{ + FILE *fp; + int volume = -1; + + fp = fopen(VOLUME_FILEPATH, "r"); + if(fp) + { + fscanf(fp, "%d", &volume); + fclose(fp); + + if(volume > 24) + volume = 24; + if(volume < -1) + volume = -1; + } + + return volume; +} + +static int getSpkVolume() +{ + FILE *fp; + int volume = 100; + + fp = fopen(SPEAKER_VOLUME_FILEPATH, "r"); + if(fp) + { + fscanf(fp, "%d", &volume); + fclose(fp); + + if(volume > 120) + volume = 120; + if(volume < 0) + volume = 0; + } + + return volume; +} + +static int sendMessageQ(int iReceiver, int iCommand, const char *strCommand) +{ + struct command_msg_st message; + int len = 0; + + memset( &message, 0, sizeof(message) ); + + message.iReceiver = iReceiver; + message.iSender = 1; + message.iCommand = iCommand; + message.iLength = strlen( strCommand ); + + if(message.iLength >= MAX_MSG_LENGTH) + message.iLength = MAX_MSG_LENGTH-1; + + strncpy( message.chData, strCommand, message.iLength ); + len = message.iLength + sizeof(long) * 3; + if(len >= MAX_MSG_LENGTH) + len = ( sizeof(message) - sizeof(long) ); + + /*if( msgsnd( g_msgqid, (void*)&message, len, IPC_NOWAIT ) == -1 ) + { + fprintf(stderr, "WARN : Can't Send Message\n"); + return 0; + }*/ + + return 1; +} + +static void setEffectorDisplay(int isOnVod, int isOnSubtitle) +{ + char effectOrder[256]; + + if(isOnVod) + { + sprintf(effectOrder, "0 0 0 1280 720 0 %d", 0x01000000); + sendMessageQ(20, 24, effectOrder); + } + else + { + sprintf(effectOrder, "0 0 0 1280 720 0 /mnt/wise/img/wait_broadcast.jpg"); + sendMessageQ(20, 1, effectOrder); + } + +} + +static void *audioScheduler(void *arg) +{ + int ret; + int schedulerState = 0; // 1 : inservice, 0 : standby + char systemOrder[512]; + pthread_detach( pthread_self() ); + + while(1) + { + struct stat sb; + +// if(access("/dev/dsp", W_OK | R_OK) >= 0) + { + pthread_mutex_lock(&g_mtxAudio); + if(strcmp(g_stbInfo.curAudioState, "inservice") == 0 && schedulerState == 0) { + sprintf(systemOrder, "streamReceiver %s &", g_stbInfo.audioService); + system(systemOrder); + schedulerState = 1; + } + pthread_mutex_unlock(&g_mtxAudio); + + pthread_mutex_lock(&g_mtxAudio); + if(strcmp(g_stbInfo.curAudioState, "inservice") != 0 && schedulerState == 1) { + system("killall streamReceiver"); + remove("/tmp/_audio_streaming"); + schedulerState = 0; + } + pthread_mutex_unlock(&g_mtxAudio); + } + + usleep(10000); + } +} + +static void *dacScheduler(void *arg) +{ + int ret; + char systemOrder[512]; + pthread_detach( pthread_self() ); + + while(1) + { + struct stat sb; + + if(access("/dev/dsp", W_OK | R_OK) >= 0) + { + ret = stat("/tmp/_on_tts", &sb); + if(ret < 0) + { + struct stat sb; + ret = stat("/tmp/_audio_streaming", &sb); + + if(ret >= 0) + { + sprintf(systemOrder, "dac_player /tmp/_audio_streaming %d RAW", getSpkVolume()); + system(systemOrder); + } + } + } + + usleep(10000); + } +} + +struct in_addr *atoaddr(char *address) +{ + struct hostent *host; + static struct in_addr saddr; + + /* First try it as aaa.bbb.ccc.ddd. */ + saddr.s_addr = inet_addr(address); + if (saddr.s_addr != (unsigned long)-1) { + return &saddr; + } + host = gethostbyname(address); + if (host != NULL) { + return (struct in_addr *) *host->h_addr_list; + } + return NULL; +} + + +static int connectRequest(char *url, int port) +{ + char *pos; + char serverAddress[128] = {0, }; + + struct in_addr *addr; + int sock, connected; + struct sockaddr_in address; + fd_set tabSocketToWait; + struct timeval timeout; + char *netaddress; + int type; + int connect_timeout; + + signal(SIGPIPE, SIG_IGN); + + if(url == NULL) + { + LOGW("Got Null pointer url!"); + return -1; + } + + if(strlen(url) < 3) + return -1; + + LOGD("TRY TO CONNECT %s:%d", url, port); + + sprintf(serverAddress, "%s", url); + + netaddress = (char *)serverAddress; + pos = strchr(netaddress, ':'); + if(pos) + { + port = atoi(pos + 1); + *pos = 0; + } + type = SOCK_STREAM; + connect_timeout = 2000; + + addr = atoaddr(netaddress); + if (addr == NULL) + { + LOGI("Server Address Problem.. \n"); + + return -1; + } + + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_port = htons(port); + address.sin_addr.s_addr = addr->s_addr; + + sock = socket(AF_INET, type, 0); + + if (type == SOCK_STREAM) { + fcntl(sock, F_SETFL, O_NONBLOCK); + connected = connect(sock, (struct sockaddr *) &address, + sizeof(address)); + if(connected < 0) { + FD_ZERO(&tabSocketToWait); + FD_SET(sock,&tabSocketToWait); + timeout.tv_sec = connect_timeout/1000; + timeout.tv_usec = (connect_timeout%1000) * 1000; + + select(sock+1, + &tabSocketToWait, + (fd_set *)0, + (fd_set *)0, + &timeout); + + connected = connect(sock, (struct sockaddr *) &address, + sizeof(address)); + + if (connected < 0) { + LOGD("Connect Error....\n"); + close(sock); + + return -1; + } + } + } + + int status; + int bufSize = 4*1024*1024; + + if(setsockopt((int)sock, SOL_SOCKET, SO_RCVBUF, (void*)&bufSize, sizeof(bufSize)) < 0) { + fprintf(stderr, "Can't change system network size (wanted size = %d) \n", bufSize); + status = -1; + } + +// struct linger { +// int l_onoff; +// int l_linger; +// }; +// struct linger _linger; +// +// _linger.l_onoff = 1; +// _linger.l_linger = 0; +// +// setsockopt((int)sock, SOL_SOCKET, SO_LINGER, &_linger, sizeof(_linger)); // Linger option + + + LOGI("CONNECT IS OK!\n"); + return sock; +} + + +static int g_process_status = -1; +static void *notifyScheduler(void *arg) +{ + char effectOrder[256]; + char systemOrder[512]; + struct stat sb; + STB_INFO stbInfo; + int tryCount = 0; + int ret; + pthread_detach( pthread_self() ); + + if(arg == NULL) + return NULL; + + memcpy(&stbInfo, arg, sizeof(STB_INFO)); + + if(g_stbInfo.isOnNotify) + { + g_stbInfo.isOnNotify = 2; + while(g_stbInfo.isOnNotify != 0) + { + usleep(20000); + } + } + + g_stbInfo.isOnNotify = 1; + + do { + sprintf(systemOrder, "wget -t 2 -T 2 %s -O /tmp/notify.png", g_stbInfo.notifyFileUrl); + system_safe(systemOrder); + ret = stat("/tmp/notify.png", &sb); + if(ret >= 0 && sb.st_size > 0) + break; + + tryCount++; + } while(tryCount < 5); + + if(tryCount == 5) { + g_stbInfo.isOnNotify = 0; + return NULL; + } + + + { + tryCount = 0; + g_process_status = 1; + //sendMessageQ(2, 1, "q"); + //usleep(100000); + sprintf(effectOrder, "0 0 0 1280 720 0 /tmp/notify.png"); + sendMessageQ(20, 1, effectOrder); + + while(g_stbInfo.isOnNotify == 1 && tryCount < 300) { + tryCount++; + usleep(100000); + } + +#if 0 + if(strcasecmp(stbInfo.curState, "inservice") == 0) + { + sprintf(g_stbInfo.targetService, "%s", stbInfo.curService); + printf("TARGET SERVICE : %s\n", g_stbInfo.targetService); + g_stbInfo.serviceTargetTime = 1; + } + else + { + if(!g_browserExist) + { + sprintf(effectOrder, "0 0 0 1280 720 0 /mnt/wise/img/wait_broadcast.jpg"); + sendMessageQ(20, 1, effectOrder); + } + else + { + setEffectorDisplay(1, 0); + } + } +#else + if(strcasecmp(g_stbInfo.curState, "inservice") == 0) + { + setEffectorDisplay(1, 0); + } + else + { + sprintf(effectOrder, "0 0 0 1280 720 0 /mnt/wise/img/wait_broadcast.jpg"); + sendMessageQ(20, 1, effectOrder); + g_process_status = -1; + } +#endif + } + + g_stbInfo.isOnNotify = 0; + + return NULL; +} + +static void *ttsScheduler(void *arg) +{ + char effectOrder[256]; + char systemOrder[512]; + char strMessage[512]; + struct stat sb; + STB_INFO stbInfo; + int tryCount = 0; + int ret; + int isConcurrentPlay; + pthread_detach( pthread_self() ); + + if(arg == NULL) + return NULL; + + memcpy(&stbInfo, arg, sizeof(STB_INFO)); + + g_stbInfo.isOnTTSPlay = 1; + + LOGI("Download TTS URL : %s", stbInfo.ttsFileUrl); + + do { + sprintf(systemOrder, "wget -t 2 -T 2 %s -O /tmp/tts.wav", stbInfo.ttsFileUrl); + system_safe(systemOrder); + ret = stat("/tmp/tts.wav", &sb); + if(ret >= 0 && sb.st_size > 0) + break; + + tryCount++; + } while(tryCount < 5); + + if(tryCount == 5) + return NULL; + + isConcurrentPlay = stbInfo.isTTSConcurrent; + if(access("/dev/dsp", W_OK | R_OK) < 0) + isConcurrentPlay = 0; + + if(isConcurrentPlay) + { + system_safe("touch /tmp/_on_tts;killall dac_player"); + sprintf(strMessage, "dac_player /mnt/wise/bin/upchimes.wav"); + if(stbInfo.ttsVolume >= 0) + sprintf(strMessage, "dac_player /mnt/wise/bin/upchimes.wav %d", (stbInfo.ttsVolume * 100 / 24)); + if(stbInfo.chimeVolume >= 0) // added by ritoseo - 2017.02.15 + sprintf(strMessage, "dac_player /mnt/wise/bin/upchimes.wav %d", (stbInfo.chimeVolume * 100 / 24)); + + system(strMessage); + + for(tryCount = 0;tryCount < stbInfo.ttsPlayCount;tryCount++) + { + sprintf(strMessage, "dac_player /tmp/tts.wav"); + if(stbInfo.ttsVolume >= 0) + sprintf(strMessage, "dac_player /tmp/tts.wav %d", (stbInfo.ttsVolume * 100 / 24)); + system(strMessage); + } + + sprintf(strMessage, "dac_player /mnt/wise/bin/downchimes.wav"); + if(stbInfo.ttsVolume >= 0) + sprintf(strMessage, "dac_player /mnt/wise/bin/downchimes.wav %d", (stbInfo.ttsVolume * 100 / 24)); + if(stbInfo.chimeVolume >= 0) // added by ritoseo - 2017.02.15 + sprintf(strMessage, "dac_player /mnt/wise/bin/downchimes.wav %d", (stbInfo.chimeVolume * 100 / 24)); + + system(strMessage); + + g_stbInfo.isOnTTSPlay = 0; + remove("/tmp/_on_tts"); + } + else + { + if(g_browserExist && g_audioCastNoUI) { + } else { + sprintf(effectOrder, "0 0 0 1280 720 0 /mnt/wise/img/on_tts.jpg"); + sendMessageQ(20, 1, effectOrder); + } + + sendMessageQ(2, 1, "q"); + sleep(1); + + remove("/tmp/_osic_playover"); + g_stbInfo.isTTSOver = 0; + sprintf(strMessage, "/mnt/wise/bin/upchimes.wav -volume %d", getVolume()); + if(stbInfo.chimeVolume >= 0) // added by ritoseo - 2017.02.15 + sprintf(strMessage, "/mnt/wise/bin/upchimes.wav -volume %d", stbInfo.chimeVolume); + + sendMessageQ(2, 2, strMessage); + while(!g_stbInfo.isTTSOver) + usleep(10000); + + for(tryCount = 0;tryCount < stbInfo.ttsPlayCount;tryCount++) + { + g_stbInfo.isTTSOver = 0; + sprintf(strMessage, "/tmp/tts.wav -volume %d", getVolume()); + //if(stbInfo.ttsVolume >= 0) + // sprintf(strMessage, "/tmp/tts.wav -volume %d", stbInfo.ttsVolume); + sendMessageQ(2, 2, strMessage); + while(!g_stbInfo.isTTSOver) + usleep(10000); + } + + g_stbInfo.isTTSOver = 0; + sprintf(strMessage, "/mnt/wise/bin/downchimes.wav -volume %d", getVolume()); + if(stbInfo.chimeVolume >= 0) // added by ritoseo - 2017.02.15 + sprintf(strMessage, "/mnt/wise/bin/downchimes.wav -volume %d", stbInfo.chimeVolume); + sendMessageQ(2, 2, strMessage); + while(!g_stbInfo.isTTSOver) + usleep(10000); + + g_stbInfo.isOnTTSPlay = 0; + + if(strcasecmp(stbInfo.curState, "inservice") == 0) + { + if(g_browserExist) { + setEffectorDisplay(1, 0); + } + sleep(2); + sprintf(g_stbInfo.targetService, "%s", stbInfo.curService); + printf("TARGET SERVICE : %s\n", g_stbInfo.targetService); + g_stbInfo.serviceTargetTime = 1; + } + /* Added by ritoseo - 2017-10-15 */ + else if(strcasecmp(stbInfo.curAudioState, "inservice") == 0) + { + char order[512]; + int volume = getVolume(); + char effectOrder[256]; + + sleep(2); + g_stbInfo.isOnASSPlay = 1; + sprintf(order, "%s", stbInfo.audioService); + if(volume >= 0) + { + sprintf(order + strlen(order), " -volume %d", volume); + } + sendMessageQ(2, 2, order); + } + /*********************************/ + else { + if(!g_browserExist) { + sprintf(effectOrder, "0 0 0 1280 720 0 /mnt/wise/img/wait_broadcast.jpg"); + sendMessageQ(20, 1, effectOrder); + } else { + setEffectorDisplay(1, 0); + } + } + } + + //system("killall -CONT test_rmfp"); + + return NULL; +} + +static void *msgqueReceiver(void *arg) +{ + int recv; + struct command_msg_st message; + + pthread_detach( pthread_self() ); + + while(1) + { + usleep(1000); + memset(&message, 0, sizeof(struct command_msg_st)); + + if(!g_browserExist) + { + /*recv = msgrcv( g_msgqid, (void*)&message, MAX_MSG_LENGTH, 1, IPC_NOWAIT );*/ + if(recv != -1) + { + char arg[128] = {0, }; + char value[512] = {0, }; + + switch(message.iCommand) + { + case MANAGER_PLAYER_STATUS: + sscanf( message.chData, "%[^=]=%[^\n]", arg, value); + if(!strncmp(arg, PLAYER_START_STATUS_DATA, strlen(PLAYER_START_STATUS_DATA))) + { + printf("PLAYER START MESSAGE!!\n\n\n"); + if(!g_stbInfo.isOnTTSPlay && !g_stbInfo.isOnASSPlay && !g_stbInfo.isOnNotify) + setEffectorDisplay(1, 0); + } + else if(!strncmp(arg, PLAYER_END_STATUS_DATA, strlen(PLAYER_END_STATUS_DATA))) + { + printf("PLAYER END MESSAGE!! : %s\n\n\n", value); + if(g_stbInfo.isOnTTSPlay) + g_stbInfo.isTTSOver = 1; + else if(!g_stbInfo.isOnNotify) { + if(g_browserExist) { + setEffectorDisplay(1, 0); + } else { + setEffectorDisplay(0, 0); + } + if(g_process_status > 0) + g_process_status = -1; + } + } + } + } + } + + /*recv = msgrcv( g_msgqid, (void *)&message, MAX_MSG_LENGTH, 21, IPC_NOWAIT ); // Message From rdid_effector*/ + if( recv != -1 ) + { + if(message.iCommand != STRING_NOTIFY_COMMAND) + { + } + else if(message.iCommand == STRING_NOTIFY_COMMAND) + { + g_stbInfo.sub_isStandby = 1; + if(g_stbInfo.sub_remainCount == 0) + { + sendMessageQ(20, 25, "4"); + if(strcasecmp(g_stbInfo.curState, "inservice") == 0 || g_browserExist) + sendMessageQ(20, 23, "4"); + } + } + } + } +} + +static void *bcastReceiver(void *arg) +{ + int ret; + int sock = -1; + int recv; + int showDuration = 30; // DEFAULT 30 SEC. + int filledSize; + int cur_status; + int process_status; + char effectOrder[256]; + char serverMessage[4096] = {0, }; + char handleMessage[4096] = {0, }; + char strDownloadUrl[512]; + time_t showStartTime = 0; + + pthread_detach( pthread_self() ); + + process_status = cur_status = -1; + filledSize = 0; + remove("/tmp/notify.png"); + + while(1) + { +#if 1 + if(sock < 0) + { + ret = connectRequest(g_stbInfo.serverAddr, BCAST_SERVER_PORT); + if(ret >= 0) { + sock = ret; + } + } + + memset(strDownloadUrl, 0, sizeof(strDownloadUrl)); + + if(sock >= 0) + { + recv = socketRecv(sock, (unsigned char *)serverMessage + filledSize, sizeof(serverMessage) - filledSize); + if(recv > 0) { + char *pPos; + char *pPosNext; + filledSize += recv; + + if((pPos = strstr(serverMessage, "\r\n\r\n")) != NULL) + { + int msgLen = ((int)(pPos - serverMessage)) + 4; + + memset(handleMessage, 0, sizeof(handleMessage)); + memcpy(handleMessage, serverMessage, msgLen); + if(filledSize > msgLen) { + memcpy(serverMessage, serverMessage + msgLen, filledSize - msgLen); + memset(serverMessage + filledSize - msgLen, 0, sizeof(serverMessage) - (filledSize - msgLen)); + } + filledSize -= msgLen; + + pPos = handleMessage; + while(pPos != NULL) + { + pPosNext = strstr(pPos, "\r\n"); + if(pPosNext) { + *pPosNext = 0; + pPosNext += 2; + } + + if(strncmp(pPos, "DURATION=", strlen("DURATION=")) == 0) { + showDuration = atoi(pPos + strlen("DURATION=")); + if(showDuration <= 0) + showDuration = 30; + + } else if(strncmp(pPos, "URL=", strlen("URL=")) == 0) { + sprintf(strDownloadUrl, "%s", pPos + strlen("URL=")); + } + + pPos = pPosNext; + } + } + } else if(recv < 0) { // Connection is disconnected + close(sock); + sock = -1; + } + } +#endif + + if(strlen(strDownloadUrl)) + { + char strOrder[1024]; + struct stat sb; + + if(strncmp(strDownloadUrl, "https://", strlen("https://")) == 0) { + sprintf(strOrder, "wget -t 5 -T 5 --no-check-certificate %s -O /tmp/notify.png", strDownloadUrl); + } else { + sprintf(strOrder, "wget -t 5 -T 5 %s -O /tmp/notify.png", strDownloadUrl); + } + system_safe(strOrder); + + ret = stat("/tmp/notify.png", &sb); + if(ret >= 0 && sb.st_size > 0) { + sprintf(effectOrder, "0 0 0 1280 720 0 /tmp/notify.png"); + sendMessageQ(20, 1, effectOrder); + showStartTime = time(NULL); + } + } + + usleep(10000); + if(showStartTime > 0) { + if(time(NULL) - showStartTime <= showDuration) { + cur_status = 1; + } else { + cur_status = -1; + showStartTime = 0; + + sprintf(effectOrder, "0 0 0 1280 720 0 %d", 0x01000000); + sendMessageQ(20, 24, effectOrder); + remove("/tmp/notify.png"); + } + } else { + cur_status = -1; + } + + g_process_status = cur_status; +#if 0 + process_status = cur_status; + + if(process_status < 0) // BCAST MESSAGE CLOSED + { + if(access("/tmp/_hpd_on", R_OK) >= 0) { + system_safe("gpio_control 7 off && usleep 200000 && gpio_control 7 on"); + sleep(2); + } + } + else // BCAST MESSAGE EXIST + { + if(access("/tmp/_hpd_on", R_OK) < 0) { + system_safe("gpio_control 7 off && usleep 200000 && gpio_control 7 on"); + sleep(2); + } + } +#endif + } +} + +static void char2hex(char value, char *rtv) +{ + struct bit *lbit; + char matrix[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B' ,'C', 'D', 'E', 'F'}; + lbit = (struct bit *)&value; + sprintf( rtv,"%c%c", matrix[lbit->lh], matrix[lbit->hh] ); +} + +int get_default_gw(char *pBuffer) { + FILE *f; + char line[100] = {0, }; + char *p, *c, *g, *saveptr; + int nRet = 0; + + f = fopen("/proc/net/route", "r"); + if(f == NULL) + return 0; + + while (fgets(line, 100, f)) { + p = strtok_r(line, " \t", &saveptr); + g = strtok_r(NULL, " \t", &saveptr); + c = strtok_r(NULL, " \t", &saveptr); + + if (p != NULL && c != NULL) { + if (strcmp(c, "00000000") == 0) { + if (g) { + char *pEnd; + char part[4][6]; + int value[4]; + int i; + int ng = strtol(g, &pEnd, 16); + struct in_addr addr; + addr.s_addr = ng; + memset(&part, 0, sizeof(part)); + memcpy(part[0], g + 6, 2); + memcpy(part[1], g + 4, 2); + memcpy(part[2], g + 2, 2); + memcpy(part[3], g, 2); + for (i = 0; i < 4; i++) + sscanf(part[i], "%x", &value[i]); + + //strcpy(pBuffer, inet_ntoa(addr)); + sprintf(pBuffer, "%d.%d.%d.%d", value[0], value[1], value[2], value[3]); + nRet = 1; + } + break; + } + } + } + + fclose(f); + + return nRet; +} + +int get_interface_info( const char *eth, int infotype, char *rtnval ) +{ + int i; + int fd; + struct ifreq ifrq; + struct sockaddr_in *sin; + + fd = socket( AF_INET, SOCK_DGRAM, 0 ); + + strcpy(ifrq.ifr_name, eth); + if( ioctl(fd, infotype, &ifrq) < 0 ) + { + perror( "ioctl" ); + close(fd); + return 0; + } + + close(fd); + + switch( infotype ) + { + case SIOCGIFHWADDR: /* return mac address */ + for (i = 0; i < 6; i++) + { + char2hex( ifrq.ifr_addr.sa_data[i], rtnval ); + rtnval += 2; + } + + return 1; + break; + + case SIOCGIFADDR: /* return ip address */ + sin = (struct sockaddr_in *)&ifrq.ifr_addr; + strcpy( rtnval, inet_ntoa(sin->sin_addr) ); + + return 1; + break; + + case SIOCGIFNETMASK: + sin = (struct sockaddr_in *)&ifrq.ifr_addr; + strcpy( rtnval, inet_ntoa(sin->sin_addr) ); + + return 1; + break; + + } + + /* wrong method, return error */ + return 0; +} + + + +static int socketSend(int sock, char *sendData, int datasize) +{ + fd_set tabSocketToWait; + struct timeval timeout; + int status; + int connect_timeout = 1000; + int retryCount = 0; + int failErrno = 0; + + fcntl(sock, F_SETFL, O_NONBLOCK); + FD_ZERO(&tabSocketToWait); + FD_SET(sock,&tabSocketToWait); + timeout.tv_sec = connect_timeout/1000; + timeout.tv_usec = (connect_timeout%1000) * 1000; + + status = select(sock+1, + (fd_set *)0, + &tabSocketToWait, + (fd_set *)0, + &timeout); + + if(status > 0) + { + do + { + status = send(sock, sendData, datasize, MSG_DONTWAIT); + if(status == -1) + { + failErrno = errno; + retryCount++; + usleep(10000); + } + } while(status == -1 && (failErrno == EAGAIN || failErrno == EWOULDBLOCK) && retryCount < 10); + return status; + } + + return status; +} + +static int socketRecv(int sock, unsigned char *buffer, int maxSizeOfBuffer) +{ + int flags=0; + int actuallyReceived = -1; + int flag; + + if(!buffer) + return -2; + + memset(buffer, 0, maxSizeOfBuffer); + + flag = fcntl((int)sock, F_GETFL, 0); + fcntl((int)sock, F_SETFL, flag | O_NONBLOCK); + + flags = MSG_DONTWAIT; + + actuallyReceived=recv((int)sock,buffer,maxSizeOfBuffer,flags); + + if(actuallyReceived <0) + { + if(errno == EAGAIN) + return 0; + else + { + perror("++++++++++++++++++++++++++++ socket Recv"); + return -1; + } + } + else if (actuallyReceived == 0) + return -2; + + return actuallyReceived; +} + +static void updateStbPivotTime() +{ + gettimeofday(&g_pivotTimeInfo, NULL); +} + +static unsigned long long getStbTimeUSec() +{ + struct timeval timeinfo; + + gettimeofday(&timeinfo, NULL); + + return (timeinfo.tv_sec - g_pivotTimeInfo.tv_sec) * 1000000ULL + (timeinfo.tv_usec - g_pivotTimeInfo.tv_usec); +} + + +static void getCurrentTime(char *buf) +{ + time_t ct = time(NULL); + struct tm *tinfo; + + if(!buf) + return; + + tinfo = gmtime(&ct); + + sprintf(buf, "%d", (tinfo->tm_year + 1900) % 100); + if(tinfo->tm_mon + 1 < 10) + sprintf(buf + strlen(buf), "0"); + sprintf(buf + strlen(buf), "%d", tinfo->tm_mon + 1); + if(tinfo->tm_mday < 10) + sprintf(buf + strlen(buf), "0"); + sprintf(buf + strlen(buf), "%d", tinfo->tm_mday); + + sprintf(buf + strlen(buf), "."); + + if(tinfo->tm_hour < 10) + sprintf(buf + strlen(buf), "0"); + sprintf(buf + strlen(buf), "%d", tinfo->tm_hour); + + if(tinfo->tm_min < 10) + sprintf(buf + strlen(buf), "0"); + sprintf(buf + strlen(buf), "%d", tinfo->tm_min); + + if(tinfo->tm_sec < 10) + sprintf(buf + strlen(buf), "0"); + sprintf(buf + strlen(buf), "%d", tinfo->tm_sec); +} + +static int isEndsWith(char *pBase, char pivot) { + if(pBase == NULL) + return 0; + + int len = strlen(pBase); + if(pBase[len - 1] == pivot) + return 1; + + return 0; +} + +static void createStbMessage(MESSAGE_TYPE mType, int reportType, STB_INFO *pInfo, char *buf, int bufLen) +{ + char payload[1024] = {0,}; + + if(!buf) + return; + + if(!pInfo) + return; + + memset(buf, 0, sizeof(bufLen)); + + if(mType == MESSAGE_REGISTER) + sprintf(payload, "myaddr=%s/NULL/", g_versionString); + else + sprintf(payload, "myaddr=NULL/NULL/"); + + if(strlen(pInfo->forceMac)) { + sprintf(payload + strlen(payload), "172.16.16.16/%s", pInfo->forceMac); + } else { + get_interface_info("eth0", SIOCGIFADDR, payload + strlen(payload)); + if(isEndsWith(payload, '/')) { + get_interface_info("wlan0", SIOCGIFADDR, payload + strlen(payload)); + } + sprintf(payload + strlen(payload), "/"); + get_interface_info("eth0", SIOCGIFHWADDR, payload + strlen(payload)); + } + sprintf(payload + strlen(payload), "/%s\n", pInfo->terminalID); + + if(mType == MESSAGE_ACK) + { + sprintf(buf, "type=ack\n"); + } + else if(mType == MESSAGE_REGISTER) + { + sprintf(buf, "type=register\n"); +#if 0 + if(access("/tmp/_bcast_sid", R_OK) >= 0) // bcast mode + { + FILE *fp = fopen("/tmp/_bcast_sid", "rt"); + int station_id = 0; + int ret = 0; + if(fp) { + ret = fscanf(fp, "%d", &station_id); + fclose(fp); + } + if(ret == 1) { + sprintf(payload + strlen(payload), "station_id=%d\n", station_id); + } + } +#else + if(getenv("station_id")) // bcast mode + { + sprintf(payload + strlen(payload), "station_id=%s\n", getenv("station_id")); + } +#endif + } + else if(mType == MESSAGE_HELLO) + { + sprintf(buf, "type=hello\n"); + } + else if(mType == MESSAGE_REPORT) + { + sprintf(buf, "type=report\n"); + + if(reportType == 1) // state report + { + sprintf(payload + strlen(payload), "state="); + getCurrentTime(payload + strlen(payload)); + sprintf(payload + strlen(payload), "/%s\n", pInfo->curState); + } + else if(reportType == 2) // service report + { + sprintf(payload + strlen(payload), "service="); + getCurrentTime(payload + strlen(payload)); + sprintf(payload + strlen(payload), "/%s\n", pInfo->curService); + } + else if(reportType == 3) // onetime check + { + sprintf(payload + strlen(payload), "state="); + getCurrentTime(payload + strlen(payload)); + sprintf(payload + strlen(payload), "/%s\n", pInfo->curState); + + sprintf(payload + strlen(payload), "service="); + getCurrentTime(payload + strlen(payload)); + sprintf(payload + strlen(payload), "/%s\n", pInfo->curService); + } + + sprintf(payload + strlen(payload), "audiostatus="); + sprintf(payload + strlen(payload), "%s\n", pInfo->curAudioState); + } + + + sprintf(buf + strlen(buf), "cseq=%d\n", pInfo->cseq); + sprintf(buf + strlen(buf), "size=%d\n", strlen(payload)); + sprintf(buf + strlen(buf), "cheksum=a\n\n"); + sprintf(buf + strlen(buf), "%s", payload); +} + +static void printMsgToHex(char *message) { + int length = strlen(message); + int i; + char whole[8192] = {0, }; + + for(i = 0;i < length;i++) { + sprintf(whole + strlen(whole), "%02X ", message[i]); + } + + LOGW("[%s]",whole); +} + +static int parseServerMessage(STB_INFO *pInfo, char *message) +{ + char *pos; + char *startPos; + char dummyRead[MAX_STRING_LEN]; + char stringRead[MAX_STRING_LEN]; + char sendMessage[4096] = {0, }; + int ret; + + int isNeedAck = 0; + int isNeedHello = 0; + int isNeedOnetimeCheck = 0; + + if(pInfo == NULL) + return 0; + + if(message == NULL) + return 0; + + if(strstr(message, "type=") == NULL) + return 0; + + if(strstr(message, "\r\n\r\n") == NULL) + return 0; + + //LOGW("[%s]", message); + + startPos = message; + message = strstr(message, "type="); + + if(strncmp(message, "type=", strlen("type="))) + return 0; + + + if(!g_timeSyncDone) + { + pos = strstr(startPos, "updatetime="); + if(pos) + { + int year, month, day, hour, min, sec; + int ret; + struct tm tm_ptr; + time_t m_time; + char dayString[MAX_STRING_LEN] = {0, }; + char timeString[MAX_STRING_LEN]; + char *pos2; + + snprintf(dayString, sizeof(dayString), "%s", pos + strlen("updatetime=")); + if(atoi(dayString) > 0) + { + pos2 = strchr(dayString, '.'); + if(pos2) + *pos2 = 0; + + if(strlen(dayString) == 7) + ret = sscanf(dayString + 1, "%2d%2d%2d", &year, &month, &day); + else + ret = sscanf(dayString, "%2d%2d%2d", &year, &month, &day); + + if(ret == 3) { + pos2 = strchr(pos, '.'); + if (pos2) { + snprintf(timeString, sizeof(timeString), "%s", pos2 + 1); + sscanf(timeString, "%2d%2d%2d", &hour, &min, &sec); + } + + sprintf(timeString, "ritosysc SHELL-ORDER=date %02d%02d%02d%02d%02d.%02d", + month, day, hour, min, year % 100, sec); + //LOGW("RITO Time Set String : %s", timeString); + system_safe(timeString); + + sleep(1); + updateStbPivotTime(); + g_timeSyncDone = 1; + } + } + } + } + + + pos = message + strlen("type="); + if(strncmp(pos, "ack", strlen("ack")) == 0) // Ack from Register + { + pos = strstr(message, "myaddr="); + if(pos) + { + ret = sscanf(pos, "myaddr=%[^/]/%[^/]/%[^/]/%[^/]/%[^\n]", dummyRead, dummyRead, dummyRead, dummyRead, stringRead); + if(ret == 5) + { + sprintf(pInfo->terminalID, "%s", stringRead); + } + } + } + else if(strncmp(pos, "hello", strlen("hello")) == 0) // Control Message + { + isNeedAck = 1; + isNeedHello = 1; + } + else if(strncmp(pos, "control", strlen("control")) == 0) // Control Message + { + isNeedAck = 1; + + pos = strstr(message, "reportperiod="); + if(pos) + { + int period1, period2; + char peTarget1[128] = {0, }; + char peTarget2[128] = {0, }; + + ret = sscanf(pos, "reportperiod=%d/%[^/]/%d/%[^/]", &period1, peTarget1, &period2, peTarget2); + if(ret == 4) + { + if(period1 == 0 && period2 == 0) // need to send one time check message + { + isNeedOnetimeCheck = 1; + } + else + { + if(strncmp(peTarget1, "state", strlen("state")) == 0) + pInfo->stateReportPeriod = period1; + else if(strncmp(peTarget1, "service", strlen("service")) == 0) + pInfo->serviceReportPeriod = period1; + + if(strncmp(peTarget2, "state", strlen("state")) == 0) + pInfo->stateReportPeriod = period2; + else if(strncmp(peTarget2, "service", strlen("service")) == 0) + pInfo->serviceReportPeriod = period2; + } + } + } + + pos = strstr(message, "statechangereport="); + if(pos) + { + int actionTime; + char target[MAX_STRING_LEN] = {0, }; + ret = sscanf(pos, "statechangereport=%d/%s", &actionTime, target); + LOGI("STATE ORDER [%s]\n", pos); + if(ret == 2) + { + pInfo->stateTargetTime = getStbTimeUSec() + actionTime * 1000000ULL; + sprintf(pInfo->targetState, "%s", target); + LOGI("STATE CHANGE TO : %s\n",pInfo->targetState); + } + } + + pos = strstr(message, "serviceoption="); + if(pos) + { + int level; + ret = sscanf(pos, "serviceoption=bufferlevel/%d", &level); + if(ret == 1) + { + pInfo->bufferLevel = level; + } + } + + pos = strstr(message, "servicechangereport="); + if(pos) + { + int actionTime; + char target[MAX_STRING_LEN] = {0, }; + ret = sscanf(pos, "servicechangereport=%d/%[^\n^\r]", &actionTime, target); + LOGI("SERVICE ORDER [%s]. ret : %d\n", pos, ret); + if(ret == 2) + { + pInfo->serviceTargetTime = getStbTimeUSec() + actionTime * 1000000ULL; + + if(actionTime < 0) // For urgent situation... by ritoseo + pInfo->serviceTargetTime = 1; + + sprintf(pInfo->targetService, "%s", target); + LOGI("SERVICE CHANGE TO : %s\n", pInfo->targetService); + } + } + + pos = strstr(message, "audioservicereport="); + if(pos) + { + int actionTime; + char target[MAX_STRING_LEN] = {0, }; + ret = sscanf(pos, "audioservicereport=%d/%[^\n^\r]", &actionTime, target); + LOGI("SERVICE ORDER [%s]\n", pos); + if(ret == 2) + { + pthread_mutex_lock(&g_mtxAudio); + if(strncmp(target, "audio/STOP", strlen("audio/STOP")) == 0) { + sprintf(pInfo->curAudioState, "standby"); + } else { + sprintf(pInfo->curAudioState, "inservice"); + sprintf(pInfo->audioService, "%s", target + strlen("audio/")); + } + pthread_mutex_unlock(&g_mtxAudio); + + LOGI("AUDIO STATE [%s], Service [%s]\n", pInfo->curAudioState, pInfo->audioService); + + /* Added by ritoseo - 2016.04.15 */ + if(strcmp(pInfo->curAudioState, "inservice") == 0) + { + if(access("/dev/dsp", W_OK | R_OK) < 0) // There is no USB DAC + { + char order[512]; + int volume = getVolume(); + char effectOrder[256]; + + g_stbInfo.isOnASSPlay = 1; + sprintf(order, "%s", pInfo->audioService); + if(volume >= 0) + { + sprintf(order + strlen(order), " -volume %d", volume); + } + sendMessageQ(2, 2, order); + + if(g_browserExist && g_audioCastNoUI) { + } else { + sprintf(effectOrder, "0 0 0 1280 720 0 /mnt/wise/img/on_tts.jpg"); + sendMessageQ(20, 1, effectOrder); + } + } + } + else if(strcmp(pInfo->curAudioState, "standby") == 0) + { + g_stbInfo.isOnASSPlay = 0; + if(access("/dev/dsp", W_OK | R_OK) < 0) // There is no USB DAC + { + if(strcasecmp(g_stbInfo.curState, "inservice") == 0 && strcasecmp(g_stbInfo.targetState, "standby") != 0) + { + sprintf(g_stbInfo.targetService, "%s", g_stbInfo.curService); + g_stbInfo.serviceTargetTime = 1; + setEffectorDisplay(1, 0); + } + else + { + if(g_browserExist) { + setEffectorDisplay(1, 0); + } else { + setEffectorDisplay(0, 0); + } + sendMessageQ(2, 1, "q"); + } + } + } + /*********************************/ + + g_stbInfo.nextServiceRPT = getStbTimeUSec(); + g_stbInfo.nextStateRPT = getStbTimeUSec() + 1000000ULL; + g_stbInfo.shortTimeReportCount = 1; + LOGI("SERVICE CHANGE TO : %s\n",pInfo->audioService); + } + } + + pos = strstr(message, "svrcontroladdr="); + if(pos) + { + } + } + else if(strncmp(pos, "subtitle", strlen("subtitle")) == 0) // Control Message + { + pos = strstr(message, "window="); + if(pos) + { + int x, y, width, height; + + //printMsgToHex(message); + ret = sscanf(pos, "window=%d/%d/%d/%d", &x, &y, &width, &height); + if(ret == 4) + { + pInfo->sub_x = x; + pInfo->sub_y = y; + pInfo->sub_width = width; + pInfo->sub_height = height; + } + } + + pos = strstr(message, "aux="); + if(pos) + { + int speed, count, fcolor, bcolor; + + ret = sscanf(pos, "aux=%d/%d/%x/%x", &speed, &count, &fcolor, &bcolor); + if(ret == 4) + { + pInfo->sub_speed = speed; + pInfo->sub_count = count; + pInfo->sub_fcolor = fcolor; + pInfo->sub_bcolor = bcolor; + } + } + + pos = strstr(message, "content="); + if(pos) + { + ret = sscanf(pos, "content=%[^\r^\n]", pInfo->sub_content); + if(ret != 1) + memset(pInfo->sub_content, 0, sizeof(pInfo->sub_content)); + } + + isNeedOnetimeCheck = 1; + /*pInfo->sub_remainCount = pInfo->sub_count; + pInfo->sub_isStandby = 1; + + sendMessageQ(20, 25, "4"); + if(strcasecmp(g_stbInfo.curState, "inservice") == 0 || g_browserExist) + sendMessageQ(20, 23, "4");*/ + + LOGI("TICKER TRACE[%d]. sub_content : [%s]. %02X %02X %02X %02X %02X %02X %02X", __LINE__, pInfo->sub_content, pInfo->sub_content[0], pInfo->sub_content[1], pInfo->sub_content[2], + pInfo->sub_content[3], pInfo->sub_content[4], pInfo->sub_content[5], pInfo->sub_content[6]); + char strNotify[8192]; + sprintf(strNotify, "X=%d\r\n", pInfo->sub_x); + sprintf(strNotify + strlen(strNotify), "Y=%d\r\n", pInfo->sub_y); + sprintf(strNotify + strlen(strNotify), "WIDTH=%d\r\n", pInfo->sub_width); + sprintf(strNotify + strlen(strNotify), "HEIGHT=%d\r\n", pInfo->sub_height); + sprintf(strNotify + strlen(strNotify), "SPEED=%d\r\n", pInfo->sub_speed); + sprintf(strNotify + strlen(strNotify), "COUNT=%d\r\n", pInfo->sub_count); + sprintf(strNotify + strlen(strNotify), "FORE_COLOR=%d\r\n", pInfo->sub_fcolor); + sprintf(strNotify + strlen(strNotify), "BACK_COLOR=%d\r\n", pInfo->sub_bcolor); + sprintf(strNotify + strlen(strNotify), "TEXT=%s\r\n", pInfo->sub_content); + + callNotifyInfo(MANAGER_NOTIFY_ORDER_START_TICKER, strNotify); + } + else if(strncmp(pos, "cec", strlen("cec")) == 0) // Control Message + { + pos = strstr(message, "order="); + if(pos) + { + int cec_order; + + ret = sscanf(pos, "order=%d", &cec_order); + if(ret == 1) + { + pInfo->cec_on_order = cec_order; + } + } + + if(pInfo->cec_on_order) + { + //system_safe("cecmanager on"); + callNotifyInfo(MANAGER_NOTIFY_ORDER_CEC_CONTROL, "on"); + pInfo->cec_orderTime = getStbTimeUSec(); + pInfo->cec_on_step = 0; + } + else + { + callNotifyInfo(MANAGER_NOTIFY_ORDER_CEC_CONTROL, "off"); + //system_safe("cecmanager off"); + } + isNeedOnetimeCheck = 1; + } + else if(strncmp(pos, "tts", strlen("tts")) == 0) // Control Message + { + pos = strstr(message, "url="); + if(pos) + { + ret = sscanf(pos, "url=%[^\r^\n]", pInfo->ttsFileUrl); + if(ret != 1) + memset(pInfo->ttsFileUrl, 0, sizeof(pInfo->ttsFileUrl)); + + char *filePos = strrchr(pos, '/'); + if(filePos) { + ret = sscanf(filePos + 1, "%[^\r^\n]", pInfo->ttsFileName); + if(ret != 1) + memset(pInfo->ttsFileName, 0, sizeof(pInfo->ttsFileName)); + } + } + + pos = strstr(message, "count="); + if(pos) + { + ret = sscanf(pos, "count=%d", &pInfo->ttsPlayCount); + if(ret != 1) + pInfo->ttsPlayCount = 0; + } + + pos = strstr(message, "concurrent="); + if(pos) + { + ret = sscanf(pos, "concurrent=%d", &pInfo->isTTSConcurrent); + if(ret != 1) + pInfo->isTTSConcurrent = 0; + } + + pos = strstr(message, "volume="); + if(pos) + { + ret = sscanf(pos, "volume=%d", &pInfo->ttsVolume); + if(ret != 1) + pInfo->ttsVolume = -1; + } + + /* Added by ritoseo - 2017-02-15 */ + pos = strstr(message, "chime_vol="); + if(pos) + { + ret = sscanf(pos, "chime_vol=%d", &pInfo->chimeVolume); + if(ret != 1) + pInfo->chimeVolume = -1; + } else { + pInfo->chimeVolume = -1; + } + /*********************************/ + + char strNotify[1024]; + sprintf(strNotify, "URL=%s\r\n", pInfo->ttsFileUrl); + sprintf(strNotify + strlen(strNotify), "COUNT=%d\r\n", pInfo->ttsPlayCount); + sprintf(strNotify + strlen(strNotify), "CONCURRENT=%d\r\n", pInfo->isTTSConcurrent); + sprintf(strNotify + strlen(strNotify), "TTS_VOLUME=%d\r\n", pInfo->ttsVolume); + sprintf(strNotify + strlen(strNotify), "CHIME_VOLUME=%d\r\n", pInfo->chimeVolume); + callNotifyInfo(MANAGER_NOTIFY_ORDER_START_TTS, strNotify); + /*pthread_t tts_tid; + pthread_create( &tts_tid, NULL, ttsScheduler, pInfo);*/ + + isNeedOnetimeCheck = 1; + } + else if(strncmp(pos, "notify", strlen("notify")) == 0) // Control Message + { + char *pMsgType = strstr(message, "msgtype="); + + pos = strstr(message, "msgtype="); + if(pos) + { + ret = sscanf(pos, "msgtype=%[^\r^\n]", pInfo->notifyMsgType); + if(ret != 1) + memset(pInfo->notifyMsgType, 0, sizeof(pInfo->notifyMsgType)); + } + + pos = strstr(pMsgType, "datetime="); + if(pos) + { + ret = sscanf(pos, "datetime=%[^\r^\n]", pInfo->notifyMsgDatetime); + if(ret != 1) + memset(pInfo->notifyMsgDatetime, 0, sizeof(pInfo->notifyMsgDatetime)); + } + + pos = strstr(pMsgType, "address="); + if(pos) + { + ret = sscanf(pos, "address=%[^\r^\n]", pInfo->notifyMsgAddress); + if(ret != 1) + memset(pInfo->notifyMsgAddress, 0, sizeof(pInfo->notifyMsgAddress)); + } + + pos = strstr(pMsgType, "address-doro="); + if(pos) + { + ret = sscanf(pos, "address-doro=%[^\r^\n]", pInfo->notifyMsgAddressDoro); + if(ret != 1) + memset(pInfo->notifyMsgAddressDoro, 0, sizeof(pInfo->notifyMsgAddressDoro)); + } + + pos = strstr(pMsgType, "address-jibun="); + if(pos) + { + ret = sscanf(pos, "address-jibun=%[^\r^\n]", pInfo->notifyMsgAddressJibun); + if(ret != 1) + memset(pInfo->notifyMsgAddressJibun, 0, sizeof(pInfo->notifyMsgAddressJibun)); + } + + pos = strstr(pMsgType, "class="); + if(pos) + { + ret = sscanf(pos, "class=%[^\r^\n]", pInfo->notifyMsgDsrClass); + if(ret != 1) + memset(pInfo->notifyMsgDsrClass, 0, sizeof(pInfo->notifyMsgDsrClass)); + } + + pos = strstr(pMsgType, "size="); + if(pos) + { + ret = sscanf(pos, "size=%[^\r^\n]", pInfo->notifyMsgDsrSize); + if(ret != 1) + memset(pInfo->notifyMsgDsrSize, 0, sizeof(pInfo->notifyMsgDsrSize)); + } + + pos = strstr(pMsgType, "desc="); + if(pos) + { + ret = sscanf(pos, "desc=%[^\r^\n]", pInfo->notifyMsgDesc); + if(ret != 1) + memset(pInfo->notifyMsgDesc, 0, sizeof(pInfo->notifyMsgDesc)); + } + + pos = strstr(pMsgType, "vehicle="); + if(pos) + { + ret = sscanf(pos, "vehicle=%[^\r^\n]", pInfo->notifyMsgVehicle); + if(ret != 1) + memset(pInfo->notifyMsgVehicle, 0, sizeof(pInfo->notifyMsgVehicle)); + } + +// pthread_t notify_tid; +// pthread_create( ¬ify_tid, NULL, notifyScheduler, pInfo); + char strNotify[8192]; + sprintf(strNotify, "MSGTYPE=%s\r\n", pInfo->notifyMsgType); + sprintf(strNotify + strlen(strNotify), "DATETIME=%s\r\n", pInfo->notifyMsgDatetime); + if(strlen(pInfo->notifyMsgAddress)) { + sprintf(strNotify + strlen(strNotify), "ADDRESS=%s\r\n", pInfo->notifyMsgAddress); + } + if(strlen(pInfo->notifyMsgAddressDoro)) { + sprintf(strNotify + strlen(strNotify), "ADDRESS-DORO=%s\r\n", pInfo->notifyMsgAddressDoro); + } + if(strlen(pInfo->notifyMsgAddressJibun)) { + sprintf(strNotify + strlen(strNotify), "ADDRESS-JIBUN=%s\r\n", pInfo->notifyMsgAddressJibun); + } + sprintf(strNotify + strlen(strNotify), "CLASS=%s\r\n", pInfo->notifyMsgDsrClass); + sprintf(strNotify + strlen(strNotify), "SIZE=%s\r\n", pInfo->notifyMsgDsrSize); + sprintf(strNotify + strlen(strNotify), "DESC=%s\r\n", pInfo->notifyMsgDesc); + sprintf(strNotify + strlen(strNotify), "VEHICLE=%s\r\n", pInfo->notifyMsgVehicle); + callNotifyInfo(MANAGER_NOTIFY_ORDER_NOTIFY_SCREEN_ORDER, strNotify); + isNeedOnetimeCheck = 1; + } + else if(strncmp(pos, "json", strlen("json")) == 0) // Control Message by JSON + { + pos = strstr(message, "url="); + if(pos) + { + ret = sscanf(pos, "url=%[^\r^\n]", pInfo->notifyFileUrl); + if(ret != 1) + memset(pInfo->notifyFileUrl, 0, sizeof(pInfo->notifyFileUrl)); + } + + pos = strstr(message, "type="); + if(pos) + { + ret = sscanf(pos, "type=%[^\r^\n]", pInfo->notifyFileName); + if(ret != 1) + memset(pInfo->notifyFileUrl, 0, sizeof(pInfo->notifyFileName)); + } + + char strNotify[1024]; + sprintf(strNotify, "URL=%s\r\n", pInfo->notifyFileUrl); + sprintf(strNotify + strlen(strNotify), "TYPE=%s\r\n", pInfo->notifyFileName); + callNotifyInfo(MANAGER_NOTIFY_ORDER_JSON_ORDER, strNotify); + isNeedOnetimeCheck = 1; + } + else if(strncmp(pos, "event", strlen("event")) == 0) // Control Message EVENT + { + pos = strstr(message, "event="); + if(pos) + { + ret = sscanf(pos, "event=%[^\r^\n]", pInfo->eventType); + if(ret != 1) + memset(pInfo->eventType, 0, sizeof(pInfo->eventType)); + } + + pos = strstr(message, "class_id="); + if(pos) + { + ret = sscanf(pos, "class_id=%[^\r^\n]", pInfo->stbClassId); + if(ret != 1) + memset(pInfo->stbClassId, 0, sizeof(pInfo->stbClassId)); + } + + pos = strstr(message, "base_url="); + if(pos) + { + ret = sscanf(pos, "base_url=%[^\r^\n]", pInfo->eventBaseUrl); + if(ret != 1) + memset(pInfo->eventBaseUrl, 0, sizeof(pInfo->eventBaseUrl)); + } + + pos = strstr(message, "json_raw="); + if(pos) + { + ret = sscanf(pos, "json_raw=%[^\r^\n]", pInfo->eventJsonRaw); + if(ret != 1) + memset(pInfo->eventJsonRaw, 0, sizeof(pInfo->eventJsonRaw)); + } + + char strNotify[2048]; + sprintf(strNotify, "EVENT=%s\r\n", pInfo->eventType); + if(strcmp(pInfo->eventType, "image-update") == 0) { + sprintf(strNotify + strlen(strNotify), "CLASS_ID=%s\r\n", pInfo->stbClassId); + sprintf(strNotify + strlen(strNotify), "BASE_URL=%s\r\n", pInfo->eventBaseUrl); + } else if(strcmp(pInfo->eventType, "channel-update") == 0) { + sprintf(strNotify + strlen(strNotify), "JSON_RAW=%s\r\n", pInfo->eventJsonRaw); + } + callNotifyInfo(MANAGER_NOTIFY_ORDER_EVENT_MESSAGE, strNotify); + isNeedOnetimeCheck = 1; + } + + if(isNeedAck) + { + createStbMessage(MESSAGE_ACK, 1, pInfo, sendMessage, sizeof(sendMessage)); + socketSend(pInfo->sock, sendMessage, strlen(sendMessage)); + LOGI("[SEND MESSAGE]\n%s\n", sendMessage); + } + + if(isNeedHello) + { + createStbMessage(MESSAGE_HELLO, 1, pInfo, sendMessage, sizeof(sendMessage)); + socketSend(pInfo->sock, sendMessage, strlen(sendMessage)); + LOGI("[SEND MESSAGE]\n%s\n", sendMessage); + } + + if(isNeedOnetimeCheck) + { + createStbMessage(MESSAGE_REPORT, 3, pInfo, sendMessage, sizeof(sendMessage)); + socketSend(pInfo->sock, sendMessage, strlen(sendMessage)); + pInfo->lastServiceRPT = getStbTimeUSec(); + pInfo->lastStateRPT = getStbTimeUSec(); + pInfo->nextStateRPT = getStbTimeUSec(); + + LOGI("[SEND MESSAGE]\n%s\n", sendMessage); + } + + char *pTermPos = strstr(message, "\r\n\r\n"); + return (pTermPos - startPos) + 4; +} + +static void sendUnicastHeartbeat(char *address, int port, int isStart) +{ + int idx; + char sendMessage[32]; + struct sockaddr_in udp_group; + int send_s; + + memset(&udp_group, 0, sizeof(udp_group)); + + udp_group.sin_family = AF_INET; + udp_group.sin_port = htons(port); // port + inet_pton(AF_INET, address, &udp_group.sin_addr); + + if((send_s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + fprintf(stderr, "Can't create send socket\n"); + return; + } + + memset(sendMessage, 0, sizeof(sendMessage)); + + idx = 0; + sendMessage[idx++] = 0x00; + sendMessage[idx++] = 0x01; + sendMessage[idx++] = 0x00; + sendMessage[idx++] = 0x00; +#if 0 + sendMessage[idx++] = 0x75; + sendMessage[idx++] = 0x62; +#else + sendMessage[idx++] = ((g_stbInfo.unicastRecvPort & 0xFF00) >> 8); + sendMessage[idx++] = (g_stbInfo.unicastRecvPort & 0xFF); +#endif + sendMessage[idx++] = 0x00; + sendMessage[idx++] = 0x00; + + if(isStart) + sendMessage[1] = 0x01; + else + sendMessage[1] = 0x02; + + sendto(send_s, sendMessage, idx, 0, (struct sockaddr*)&udp_group, sizeof(udp_group)); + close(send_s); +} + +int getConfigValue(char *pType, char *pResult) +{ + FILE *fp; + int ret = 0; + if(!pType || !pResult) + return 0; + + fp = fopen("/metadata/config.data", "rt"); + if(fp) + { + char strLine[512]; + char strType[512]; + char strData[512]; + char *pPos; + while(!feof(fp)) + { + memset(strLine, 0, sizeof(strLine)); + pPos = fgets(strLine, sizeof(strLine), fp); + + sscanf(strLine, "%[^|]||%s", strType, strData); + if(strcmp(strType, pType) == 0) { + sprintf(pResult, "%s", strData); + ret = 1; + break; + } + } + + fclose(fp); + } + + return ret; +} + + +int get_server_addr(char *pBuffer) { + char strBuffer[512] = {0, }; + int ret = getConfigValue("svraddr", strBuffer); + LOGI("get_server_addr : %s", strBuffer); + if(pBuffer) { + sprintf(pBuffer, "%s", strBuffer); + } + + return ret; +} + +int get_app_mode(char *pBuffer) { + char strBuffer[512] = {0, }; + int ret = getConfigValue("app_mode", strBuffer); + LOGI("get_app_mode : %s", strBuffer); + + if(ret == 0) { + sprintf(strBuffer, "passive"); // Default mode is passive + } + + if(pBuffer) { + sprintf(pBuffer, "%s", strBuffer); + } + + return ret; +} + +int get_server_port() { + int port = 0; + char strBuffer[512] = {0, }; + getConfigValue("svrport", strBuffer); + port = atoi(strBuffer); + if(port == 0) + port = 10000; + + return port; +} + +void quit_osic9_mode() { + g_serviceAlive = 0; +} + +int main_osic9_mode(void *pArg) +{ + int ret; + int count; + int status; + int consecutive_count = 0; + static char sendMessage[32768] = {0, }; + static char serverMessage[32768] = {0, }; + int messageSize = 0; + char *envpos; + memset(&g_stbInfo, 0, sizeof(STB_INFO)); + + g_timeSyncDone = 0; + g_audioCastNoUI = 1; + g_serviceAlive = 1; + + g_stbInfo.cseq = 1; + g_stbInfo.sock = -1; + g_stbInfo.chimeVolume = -1; + sprintf(g_stbInfo.curState, "standby"); + sprintf(g_stbInfo.curService, "NULL/NULL/u"); + sprintf(g_stbInfo.curAudioState, "standby"); + + envpos = getenv("force_mac"); + if(envpos) + sprintf(g_stbInfo.forceMac, "%s", envpos); + + /*envpos = getenv("server_addr"); + if(envpos) + sprintf(g_stbInfo.serverAddr, "%s", envpos); + else + sprintf(g_stbInfo.serverAddr, "172.16.135.8");*/ + + get_server_addr(g_stbInfo.serverAddr); + //sprintf(g_stbInfo.serverAddr, "192.168.1.242"); + + /*envpos = getenv("server_port"); + if(envpos) + g_stbInfo.serverPort = atoi(envpos); + else + g_stbInfo.serverPort = SERVER_PORT;*/ + + g_stbInfo.serverPort = get_server_port(); + if(g_stbInfo.serverPort == 0) { + g_stbInfo.serverPort = SERVER_PORT; + } + + sprintf(g_stbInfo.terminalID, "NULL"); + //g_stbInfo.serviceReportPeriod = 5; + //g_stbInfo.stateReportPeriod = 5; + g_stbInfo.serviceReportPeriod = 1; // Minute + g_stbInfo.stateReportPeriod = 1; // Minute + g_stbInfo.shortTimeReportCount = 0; + g_stbInfo.bufferLevel = 40; // default level + + updateStbPivotTime(); + + pthread_mutex_init(&g_mtxAudio, NULL); + + //pthread_create( &g_tMsgRecv, NULL, msgqueReceiver, NULL); + //pthread_create( &g_tDac, NULL, dacScheduler, NULL); + pthread_create( &g_tAudio, NULL, audioScheduler, NULL); + //pthread_create( &g_tBcast, NULL, bcastReceiver, NULL); + //pthread_create( &g_tBcast, NULL, bcastExchangeManager, NULL); + + g_stbInfo.lastServiceRPT = getStbTimeUSec(); + g_stbInfo.lastStateRPT = getStbTimeUSec(); + g_stbInfo.nextServiceRPT = getStbTimeUSec() + 1000000ULL * 60ULL; + g_stbInfo.nextStateRPT = getStbTimeUSec() + 1000000ULL * 60ULL; + +#if 0 + createStbMessage(MESSAGE_ACK, 1, &g_stbInfo, sendMessage, sizeof(sendMessage)); + printf("[SEND MESSAGE]\n%s\n", sendMessage); + + createStbMessage(MESSAGE_REGISTER, 1, &g_stbInfo, sendMessage, sizeof(sendMessage)); + printf("[SEND MESSAGE]\n%s\n", sendMessage); + + createStbMessage(MESSAGE_REPORT, 1, &g_stbInfo, sendMessage, sizeof(sendMessage)); + printf("[SEND MESSAGE]\n%s\n", sendMessage); + + createStbMessage(MESSAGE_REPORT, 2, &g_stbInfo, sendMessage, sizeof(sendMessage)); + printf("[SEND MESSAGE]\n%s\n", sendMessage); + + createStbMessage(MESSAGE_REPORT, 3, &g_stbInfo, sendMessage, sizeof(sendMessage)); + printf("[SEND MESSAGE]\n%s\n", sendMessage); +#endif + + /* Added by ritoseo - 2017-04-01 */ + g_browserExist = 1; + + while(g_serviceAlive) + { + int browserModeServiceCheckCount = 0; + + ret = connectRequest(g_stbInfo.serverAddr, g_stbInfo.serverPort); + + if(ret >= 0) + { + system_safe("miscctrl function_led lnk on"); + g_stbInfo.sock = ret; + + createStbMessage(MESSAGE_REGISTER, 1, &g_stbInfo, sendMessage, sizeof(sendMessage)); + //printf("[SEND MESSAGE]\n%s\n", sendMessage); + LOGI("[SEND MESSAGE] %s\n", sendMessage); + ret = socketSend(g_stbInfo.sock, sendMessage, strlen(sendMessage)); + + messageSize = 0; + memset(serverMessage, 0, sizeof(serverMessage)); + while(count < 400 && g_serviceAlive > 0) + { + count++; + status = socketRecv(g_stbInfo.sock, (unsigned char *)serverMessage + messageSize, sizeof(serverMessage) - messageSize); + if(status > 0) + { + messageSize += status; + //printf("[RECV MESSAGE]\n%s\n", serverMessage); + LOGI("[RECV MESSAGE] %s\n", serverMessage); + ret = parseServerMessage(&g_stbInfo, serverMessage); + if(ret > 0) { + if(ret == messageSize) { + messageSize = 0; + memset(serverMessage, 0, sizeof(serverMessage)); + } else if(ret < messageSize) { + memmove(serverMessage, serverMessage + ret, messageSize - ret); + messageSize -= ret; + memset(serverMessage + messageSize, 0, sizeof(serverMessage) - messageSize); + } + break; + } + } + usleep(10000); + } + + if(strlen(g_stbInfo.terminalID) == 0) + { + close(g_stbInfo.sock); + g_stbInfo.sock = -1; + sleep(1); + continue; + } + } else { // 서버 접속 불가 + system_safe("miscctrl function_led lnk off"); + } + + + while(g_stbInfo.sock >= 0 && g_serviceAlive > 0) + { + //status = socketRecv(g_stbInfo.sock, (unsigned char *)serverMessage, sizeof(serverMessage)); + status = socketRecv(g_stbInfo.sock, (unsigned char *)serverMessage + messageSize, sizeof(serverMessage) - messageSize); + if(status > 0) + { + LOGI("[RECV MESSAGE<%d>] %s", status, serverMessage + messageSize); + messageSize += status; + //LOGI("[RECV MESSAGE] %s", serverMessage); + } + else if(status < 0) // Connection is disconnected + break; + + if(access("/tmp/msgdump", R_OK) >= 0 && messageSize > 0) { + FILE *fp = fopen("/tmp/message", "wb"); + if(fp) { + fwrite(serverMessage, 1, messageSize, fp); + fclose(fp); + } + remove("/tmp/msgdump"); + } + + if(messageSize > 0) { + //parseServerMessage(&g_stbInfo, serverMessage); + ret = parseServerMessage(&g_stbInfo, serverMessage); + if(ret > 0) { + if(ret == messageSize) { + messageSize = 0; + memset(serverMessage, 0, sizeof(serverMessage)); + } else if(ret < messageSize) { + memmove(serverMessage, serverMessage + ret, messageSize - ret); + messageSize -= ret; + memset(serverMessage + messageSize, 0, sizeof(serverMessage) - messageSize); + } + } + } + + //if(g_stbInfo.serviceReportPeriod > 0 && (getStbTimeUSec() - g_stbInfo.lastServiceRPT >= g_stbInfo.serviceReportPeriod * 1000000ULL * 60ULL || g_stbInfo.lastServiceRPT == 0)) // SERVICE REPORT + if(g_stbInfo.serviceReportPeriod > 0 && (getStbTimeUSec() > g_stbInfo.nextServiceRPT)) // SERVICE REPORT + { + createStbMessage(MESSAGE_REPORT, 2, &g_stbInfo, sendMessage, sizeof(sendMessage)); + ret = socketSend(g_stbInfo.sock, sendMessage, strlen(sendMessage)); + g_stbInfo.lastServiceRPT = getStbTimeUSec(); + g_stbInfo.nextServiceRPT = getStbTimeUSec() + g_stbInfo.serviceReportPeriod * 1000000ULL * 60ULL; + if(ret <= 0) + { + printf("FAIL TO REPORT : %d\n", __LINE__); + break; + } + //printf("[SEND MESSAGE]\n%s\n", sendMessage); + LOGI("[SEND REPORT2]%s", sendMessage); + } + + //if(g_stbInfo.stateReportPeriod > 0 && (getStbTimeUSec() - g_stbInfo.lastStateRPT >= g_stbInfo.stateReportPeriod * 1000000ULL * 60ULL || g_stbInfo.lastStateRPT == 0)) // STATE REPORT + if(g_stbInfo.stateReportPeriod > 0 && (getStbTimeUSec() > g_stbInfo.nextStateRPT)) // STATE REPORT + { + createStbMessage(MESSAGE_REPORT, 1, &g_stbInfo, sendMessage, sizeof(sendMessage)); + ret = socketSend(g_stbInfo.sock, sendMessage, strlen(sendMessage)); + g_stbInfo.lastStateRPT = getStbTimeUSec(); + if(g_stbInfo.shortTimeReportCount > 0) + { + g_stbInfo.shortTimeReportCount--; + g_stbInfo.nextStateRPT = getStbTimeUSec() + g_stbInfo.stateReportPeriod * 1000000ULL; + } + else + g_stbInfo.nextStateRPT = getStbTimeUSec() + g_stbInfo.stateReportPeriod * 1000000ULL * 60ULL; + if(ret <= 0) + { + printf("FAIL TO REPORT : %d\n", __LINE__); + break; + } + //printf("[SEND MESSAGE]\n%s\n", sendMessage); + LOGI("[SEND REPORT1]%s", sendMessage); + } + + if(g_stbInfo.lastSendHBT > 0 && getStbTimeUSec() >= g_stbInfo.lastSendHBT + 29 * 1000000ULL) // SEND UNICAST HEARTBEAT + { + g_stbInfo.lastSendHBT = getStbTimeUSec(); + sendUnicastHeartbeat(g_stbInfo.unicastAddr, g_stbInfo.unicastPort, 1); + } + + if(g_stbInfo.serviceTargetTime > 0 && getStbTimeUSec() >= g_stbInfo.serviceTargetTime && !g_stbInfo.isOnTTSPlay) // SERVICE CHANGE + { + sprintf(g_stbInfo.curService, "%s", g_stbInfo.targetService); + g_stbInfo.serviceTargetTime = 0; + + sprintf(g_stbInfo.curState, "inservice"); + printf("SERVICE : %s\n", g_stbInfo.curService); + LOGI("SERVICE : %s\n", g_stbInfo.curService); + + if(g_stbInfo.lastSendHBT > 0) + { + g_stbInfo.lastSendHBT = 0; + sendUnicastHeartbeat(g_stbInfo.unicastAddr, g_stbInfo.unicastPort, 0); + } + + if(strncmp(g_stbInfo.curService, "service", strlen("service")) == 0) + { + char order[MAX_STRING_LEN] = {0, }; + char *pos; + int volume; + int service_type = MANAGER_NOTIFY_ORDER_START_PLAYBACK; + + g_stbInfo.isOnASSPlay = 0; + pos = strstr(g_stbInfo.curService, "json://"); + if(pos) + { + sprintf(order, "%s", pos + strlen("json://")); + service_type = MANAGER_NOTIFY_ORDER_JSON_ORDER; + } + + pos = strstr(g_stbInfo.curService, "live://"); + if(pos) + { + sprintf(order, "mc://%s", pos + strlen("live://")); + + pos = strchr(order + strlen("mc://"), ':'); + if(pos) + { + *pos = '_'; + } + } + + pos = strstr(g_stbInfo.curService, "vod://"); + if(pos) + { + char *pos2; + int sendport; + int recvport; + + sprintf(order, "mc://%s", pos + strlen("vod://")); + + pos2 = strstr(order, "/uc_port="); + if(pos2) + { + *pos2 = 0; + pos2++; + + sscanf(pos2, "uc_port=%d", &sendport); + memset(pos2, 0, strlen("uc_port=")); + g_stbInfo.unicastPort = sendport; + } + + printf("SENDPORT : %d\n", g_stbInfo.unicastPort); + + pos = strchr(order + strlen("mc://"), ':'); + if(pos) + { + *pos = '_'; + + sprintf(g_stbInfo.unicastAddr, "%s", order + strlen("mc://")); + pos = strchr(g_stbInfo.unicastAddr + strlen("mc://"), '_'); + if(pos) + { + *pos = 0; + g_stbInfo.unicastRecvPort = atoi(pos + 1); + } + + printf("SENDADDR : %s\n", g_stbInfo.unicastAddr); + sendUnicastHeartbeat(g_stbInfo.unicastAddr, g_stbInfo.unicastPort, 1); + g_stbInfo.lastSendHBT = getStbTimeUSec(); + //sprintf(pos + 1, "%d", port); + } + } + + pos = strstr(g_stbInfo.curService, "hsact://"); + if(pos) + { + char *pos2; + int sendport; + int recvport; + + sprintf(order, "%s", pos); + } + + pos = strstr(g_stbInfo.curService, "assm://"); + if(pos) + { + char *pos2; + int sendport; + int recvport; + + sprintf(order, "%s", pos); + g_stbInfo.isOnASSPlay = 1; + } + + pos = strstr(g_stbInfo.curService, "asst://"); + if(pos) + { + char *pos2; + int sendport; + int recvport; + + sprintf(order, "%s", pos); + g_stbInfo.isOnASSPlay = 1; + } + + volume = getVolume(); + if(volume >= 0) + { + if(strncmp(order, "mc://", strlen("mc://")) == 0) + sprintf(order + strlen(order), "~%d", volume); + else + sprintf(order + strlen(order), " -volume %d", volume); + } + + if(service_type == MANAGER_NOTIFY_ORDER_START_PLAYBACK) { + sprintf(order + strlen(order), " -buffer_level=%d", g_stbInfo.bufferLevel); + + printf("ORDER : %s\n", order); + LOGI("ORDER : %s\n", order); + //sendMessageQ(2, 2, order); + + callNotifyInfo(MANAGER_NOTIFY_ORDER_START_PLAYBACK, order); + g_process_status = 1; + + if (!g_browserExist) { + if (g_stbInfo.isOnASSPlay) { + char effectOrder[256]; + sprintf(effectOrder, "0 0 0 1280 720 0 /mnt/wise/img/on_tts.jpg"); + sendMessageQ(20, 1, effectOrder); + } + } + } else if(service_type == MANAGER_NOTIFY_ORDER_JSON_ORDER) { + char strNotify[1024]; + sprintf(strNotify, "URL=%s\r\n", order); + if(strstr(order, "ipcast")) { + sprintf(strNotify + strlen(strNotify), "TYPE=ipcast\r\n"); + } else if(strstr(order, "carcast")) { + sprintf(strNotify + strlen(strNotify), "TYPE=carcast\r\n"); + } else if(strstr(order, "filecast")) { + sprintf(strNotify + strlen(strNotify), "TYPE=filecast\r\n"); + } + callNotifyInfo(MANAGER_NOTIFY_ORDER_JSON_ORDER, strNotify); + } + } + + g_stbInfo.nextServiceRPT = getStbTimeUSec(); + g_stbInfo.nextStateRPT = getStbTimeUSec() + 1000000ULL; + g_stbInfo.shortTimeReportCount = 1; + + //g_stbInfo.lastStateRPT = 0; + } + + if(g_stbInfo.stateTargetTime > 0 && getStbTimeUSec() >= g_stbInfo.stateTargetTime) // STATE CHANGE + { + sprintf(g_stbInfo.curState, "%s", g_stbInfo.targetState); + printf("STATE CHANGE [%s]\n", g_stbInfo.curState); + if(strcmp(g_stbInfo.curState, "standby") == 0) { + sprintf(g_stbInfo.curService, "NULL/NULL"); + printf("curService CHANGED TO NULL!!!!!!!!!!!!!!!!!!!\n"); + } + g_stbInfo.stateTargetTime = 0; + memset(g_stbInfo.targetState, 0, sizeof(g_stbInfo.targetState)); + + if(g_stbInfo.lastSendHBT > 0) + { + g_stbInfo.lastSendHBT = 0; + sendUnicastHeartbeat(g_stbInfo.unicastAddr, g_stbInfo.unicastPort, 0); + } + + if(strncmp(g_stbInfo.curState, "reboot", strlen("reboot")) == 0) + { + callNotifyInfo(100, "reboot"); + system_safe("reboot"); + } + else if(strncmp(g_stbInfo.curState, "standby", strlen("standby")) == 0) + { + callNotifyInfo(MANAGER_NOTIFY_ORDER_STOP_PLAYBACK, ""); + //sendMessageQ(2, 1, "q"); + } + else if(strncmp(g_stbInfo.curState, "pwroff", strlen("pwroff")) == 0) + { + } + + g_stbInfo.lastStateRPT = 0; + g_stbInfo.nextStateRPT = getStbTimeUSec() + 1000000ULL; + } + + if(g_stbInfo.sub_isStandby && g_stbInfo.sub_remainCount) // SUBTITLE SCHEDULING + { + char order[MAX_STRING_LEN] = {0, }; + FILE *fp; + + fp = fopen("/tmp/subtitle_info", "wt"); + if(fp) + { + fwrite(g_stbInfo.sub_content, 1, strlen(g_stbInfo.sub_content), fp); + fclose(fp); + + sprintf(order, "%d %d %d %d %d %x %x %d %d %d /tmp/subtitle_info", g_stbInfo.sub_x, g_stbInfo.sub_y, g_stbInfo.sub_width, g_stbInfo.sub_height, g_stbInfo.sub_height, + g_stbInfo.sub_fcolor, g_stbInfo.sub_bcolor, 0, 0, g_stbInfo.sub_speed); + + printf("SEND ORDER : %s\n", order); + sendMessageQ(20, 21, order); + } + + g_stbInfo.sub_remainCount--; + g_stbInfo.sub_isStandby = 0; + } + + if(g_stbInfo.cec_on_order && g_stbInfo.cec_orderTime) + { + if(g_stbInfo.cec_on_step <= 8 && getStbTimeUSec() - g_stbInfo.cec_orderTime >= (g_stbInfo.cec_on_step + 2) * 1000000) + { + system_safe("cecmanager on"); + g_stbInfo.cec_on_step += 2; + } + + if(getStbTimeUSec() - g_stbInfo.cec_orderTime >= 16000000) + { + system_safe("cecmanager on"); + g_stbInfo.cec_orderTime = 0; + g_stbInfo.cec_on_step = 2; + } + } + /*********************************/ + + usleep(10000); + } + + if(g_stbInfo.sock >= 0) { + close(g_stbInfo.sock); + g_stbInfo.sock = -1; + } + + sleep(1); + } + + pthread_mutex_destroy(&g_mtxAudio); + + return 0; +} + +void setStateTarget(char *pTarget) { + sprintf(g_stbInfo.targetState, "%s", pTarget); + g_stbInfo.stateTargetTime = 1; +} + +void sendSocketMessage(char *pTarget) { + LOGI("[sendSocketMessage] %s", pTarget); + socketSend(g_stbInfo.sock, pTarget, strlen(pTarget)); +} + +void *osic_start_thread(void *pArg) +{ + pthread_detach(pthread_self()); + int ret = main_osic9_mode(pArg); + LOGI("OSIC9_RET : %d", ret); + return NULL; +} + +void setVersionString(const char *pVersion) { + if(pVersion == NULL) + return; + + sprintf(g_versionString, "%s", pVersion); +} \ No newline at end of file diff --git a/app/src/main/cpp/osic-manager.h b/app/src/main/cpp/osic-manager.h new file mode 100644 index 0000000..3e980f9 --- /dev/null +++ b/app/src/main/cpp/osic-manager.h @@ -0,0 +1,28 @@ +#ifndef OSIC_MANAGER_H +#define OSIC_MANAGER_H + +static int MANAGER_NOTIFY_ORDER_START_PLAYBACK = 1; +static int MANAGER_NOTIFY_ORDER_STOP_PLAYBACK = 2; +static int MANAGER_NOTIFY_ORDER_START_TICKER = 3; +static int MANAGER_NOTIFY_ORDER_START_TTS = 4; +static int MANAGER_NOTIFY_ORDER_CEC_CONTROL = 5; +static int MANAGER_NOTIFY_ORDER_JSON_ORDER = 6; +static int MANAGER_NOTIFY_ORDER_NOTIFY_SCREEN_ORDER = 7; +static int MANAGER_NOTIFY_ORDER_EVENT_MESSAGE = 8; +static int MANAGER_NOTIFY_ORDER_START_AUDIO = 9; +static int MANAGER_NOTIFY_ORDER_STOP_AUDIO = 10; + +int callNotifyInfo(int code, char* strValue); +int get_interface_info( const char *eth, int infotype, char *rtnval ); +int get_default_gw(char *pBuffer); +int get_server_addr(char *pBuffer); +int get_server_port(); +int getConfigValue(char *pType, char *pResult); +void quit_osic9_mode(); +void *osic_start_thread(void *pArg); +void setVersionString(const char *pVersion); +void setStateTarget(char *pTarget); +void sendSocketMessage(char *pTarget); + + +#endif \ No newline at end of file diff --git a/app/src/main/cpp/pcm.c b/app/src/main/cpp/pcm.c new file mode 100644 index 0000000..8fd2244 --- /dev/null +++ b/app/src/main/cpp/pcm.c @@ -0,0 +1,1369 @@ +/* pcm.c +** +** Copyright 2011, The Android Open Source Project +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +** DAMAGE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#define __force +#define __bitwise +#define __user +#include + +#include "tinyalsa/asoundlib.h" + +#define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL + +/* Logs information into a string; follows snprintf() in that + * offset may be greater than size, and though no characters are copied + * into string, characters are still counted into offset. */ +#define STRLOG(string, offset, size, ...) \ + do { int temp, clipoffset = offset > size ? size : offset; \ + temp = snprintf(string + clipoffset, size - clipoffset, __VA_ARGS__); \ + if (temp > 0) offset += temp; } while (0) + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#define LOG_TAG "TINY-ALSA" + +#define ALOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args) +#define ALOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args) +/* refer to SNDRV_PCM_ACCESS_##index in sound/asound.h. */ +static const char * const access_lookup[] = { + "MMAP_INTERLEAVED", + "MMAP_NONINTERLEAVED", + "MMAP_COMPLEX", + "RW_INTERLEAVED", + "RW_NONINTERLEAVED", +}; + +/* refer to SNDRV_PCM_FORMAT_##index in sound/asound.h. */ +static const char * const format_lookup[] = { + /*[0] =*/ "S8", + "U8", + "S16_LE", + "S16_BE", + "U16_LE", + "U16_BE", + "S24_LE", + "S24_BE", + "U24_LE", + "U24_BE", + "S32_LE", + "S32_BE", + "U32_LE", + "U32_BE", + "FLOAT_LE", + "FLOAT_BE", + "FLOAT64_LE", + "FLOAT64_BE", + "IEC958_SUBFRAME_LE", + "IEC958_SUBFRAME_BE", + "MU_LAW", + "A_LAW", + "IMA_ADPCM", + "MPEG", + /*[24] =*/ "GSM", + /* gap */ + [31] = "SPECIAL", + "S24_3LE", + "S24_3BE", + "U24_3LE", + "U24_3BE", + "S20_3LE", + "S20_3BE", + "U20_3LE", + "U20_3BE", + "S18_3LE", + "S18_3BE", + "U18_3LE", + /*[43] =*/ "U18_3BE", +#if 0 + /* recent additions, may not be present on local asound.h */ + "G723_24", + "G723_24_1B", + "G723_40", + "G723_40_1B", + "DSD_U8", + "DSD_U16_LE", +#endif +}; + +/* refer to SNDRV_PCM_SUBFORMAT_##index in sound/asound.h. */ +static const char * const subformat_lookup[] = { + "STD", +}; + +static inline int param_is_mask(int p) +{ + return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && + (p <= SNDRV_PCM_HW_PARAM_LAST_MASK); +} + +static inline int param_is_interval(int p) +{ + return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) && + (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL); +} + +static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, int n) +{ + return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]); +} + +static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n) +{ + return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]); +} + +static void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned int bit) +{ + if (bit >= SNDRV_MASK_MAX) + return; + if (param_is_mask(n)) { + struct snd_mask *m = param_to_mask(p, n); + m->bits[0] = 0; + m->bits[1] = 0; + m->bits[bit >> 5] |= (1 << (bit & 31)); + } +} + +static void param_set_min(struct snd_pcm_hw_params *p, int n, unsigned int val) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + i->min = val; + } +} + +static unsigned int param_get_min(struct snd_pcm_hw_params *p, int n) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + return i->min; + } + return 0; +} + +static void param_set_max(struct snd_pcm_hw_params *p, int n, unsigned int val) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + i->max = val; + } +} + +static unsigned int param_get_max(struct snd_pcm_hw_params *p, int n) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + return i->max; + } + return 0; +} + +static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + i->min = val; + i->max = val; + i->integer = 1; + } +} + +static unsigned int param_get_int(struct snd_pcm_hw_params *p, int n) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + if (i->integer) + return i->max; + } + return 0; +} + +static void param_set_flag(struct snd_pcm_hw_params *p, unsigned int flag) +{ + if (p != NULL) { + p->flags = flag; + } +} + +static int param_get_flag(struct snd_pcm_hw_params *p) +{ + if (p != NULL) { + return p->flags; + } + return 0; +} + + +static void param_init(struct snd_pcm_hw_params *p) +{ + int n; + + memset(p, 0, sizeof(*p)); + for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK; + n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) { + struct snd_mask *m = param_to_mask(p, n); + m->bits[0] = ~0; + m->bits[1] = ~0; + } + for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; + n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) { + struct snd_interval *i = param_to_interval(p, n); + i->min = 0; + i->max = ~0; + } + p->rmask = ~0U; + p->cmask = 0; + p->info = ~0U; +} + +#define PCM_ERROR_MAX 128 + +struct pcm { + int fd; + unsigned int flags; + int running:1; + int prepared:1; + int underruns; + unsigned int buffer_size; + unsigned int boundary; + char error[PCM_ERROR_MAX]; + struct pcm_config config; + struct snd_pcm_mmap_status *mmap_status; + struct snd_pcm_mmap_control *mmap_control; + struct snd_pcm_sync_ptr *sync_ptr; + void *mmap_buffer; + unsigned int noirq_frames_per_msec; + int wait_for_avail_min; + unsigned int subdevice; +}; + +unsigned int pcm_get_buffer_size(struct pcm *pcm) +{ + return pcm->buffer_size; +} + +const char* pcm_get_error(struct pcm *pcm) +{ + return pcm->error; +} + +unsigned int pcm_get_subdevice(struct pcm *pcm) +{ + return pcm->subdevice; +} + +static int oops(struct pcm *pcm, int e, const char *fmt, ...) +{ + va_list ap; + int sz; + + va_start(ap, fmt); + vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap); + va_end(ap); + sz = strlen(pcm->error); + + if (e) + snprintf(pcm->error + sz, PCM_ERROR_MAX - sz, + ": %s", strerror(e)); + return -1; +} + +static unsigned int pcm_format_to_alsa(enum pcm_format format) +{ + switch (format) { + case PCM_FORMAT_S32_LE: + return SNDRV_PCM_FORMAT_S32_LE; + case PCM_FORMAT_S8: + return SNDRV_PCM_FORMAT_S8; + case PCM_FORMAT_S24_3LE: + return SNDRV_PCM_FORMAT_S24_3LE; + case PCM_FORMAT_S24_LE: + return SNDRV_PCM_FORMAT_S24_LE; + default: + case PCM_FORMAT_S16_LE: + return SNDRV_PCM_FORMAT_S16_LE; + }; +} + +unsigned int pcm_format_to_bits(enum pcm_format format) +{ + switch (format) { + case PCM_FORMAT_S32_LE: + case PCM_FORMAT_S24_LE: + return 32; + case PCM_FORMAT_S24_3LE: + return 24; + default: + case PCM_FORMAT_S16_LE: + return 16; + }; +} + +unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes) +{ + return bytes / (pcm->config.channels * + (pcm_format_to_bits(pcm->config.format) >> 3)); +} + +unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames) +{ + return frames * pcm->config.channels * + (pcm_format_to_bits(pcm->config.format) >> 3); +} + +static int pcm_sync_ptr(struct pcm *pcm, int flags) { + if (pcm->sync_ptr) { + pcm->sync_ptr->flags = flags; + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0) + return -1; + } + return 0; +} + +static int pcm_hw_mmap_status(struct pcm *pcm) { + + if (pcm->sync_ptr) + return 0; + + int page_size = sysconf(_SC_PAGE_SIZE); + pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED, + pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS); + if (pcm->mmap_status == MAP_FAILED) + pcm->mmap_status = NULL; + if (!pcm->mmap_status) + goto mmap_error; + + pcm->mmap_control = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL); + if (pcm->mmap_control == MAP_FAILED) + pcm->mmap_control = NULL; + if (!pcm->mmap_control) { + munmap(pcm->mmap_status, page_size); + pcm->mmap_status = NULL; + goto mmap_error; + } + if (pcm->flags & PCM_MMAP) + pcm->mmap_control->avail_min = pcm->config.avail_min; + else + pcm->mmap_control->avail_min = 1; + + return 0; + +mmap_error: + + pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr)); + if (!pcm->sync_ptr) + return -ENOMEM; + pcm->mmap_status = &pcm->sync_ptr->s.status; + pcm->mmap_control = &pcm->sync_ptr->c.control; + if (pcm->flags & PCM_MMAP) + pcm->mmap_control->avail_min = pcm->config.avail_min; + else + pcm->mmap_control->avail_min = 1; + + pcm_sync_ptr(pcm, 0); + + return 0; +} + +static void pcm_hw_munmap_status(struct pcm *pcm) { + if (pcm->sync_ptr) { + free(pcm->sync_ptr); + pcm->sync_ptr = NULL; + } else { + int page_size = sysconf(_SC_PAGE_SIZE); + if (pcm->mmap_status) + munmap(pcm->mmap_status, page_size); + if (pcm->mmap_control) + munmap(pcm->mmap_control, page_size); + } + pcm->mmap_status = NULL; + pcm->mmap_control = NULL; +} + +static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset, + char *buf, unsigned int src_offset, + unsigned int frames) +{ + int size_bytes = pcm_frames_to_bytes(pcm, frames); + int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset); + int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset); + + /* interleaved only atm */ + if (pcm->flags & PCM_IN) + memcpy(buf + src_offset_bytes, + (char*)pcm->mmap_buffer + pcm_offset_bytes, + size_bytes); + else + memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes, + buf + src_offset_bytes, + size_bytes); + return 0; +} + +static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf, + unsigned int offset, unsigned int size) +{ + void *pcm_areas; + int commit; + unsigned int pcm_offset, frames, count = 0; + + while (size > 0) { + frames = size; + pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames); + pcm_areas_copy(pcm, pcm_offset, buf, offset, frames); + commit = pcm_mmap_commit(pcm, pcm_offset, frames); + if (commit < 0) { + oops(pcm, errno, "failed to commit %d frames\n", frames); + return commit; + } + + offset += commit; + count += commit; + size -= commit; + } + return count; +} + +int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, + struct timespec *tstamp) +{ + int frames; + int rc; + snd_pcm_uframes_t hw_ptr; + + if (!pcm_is_ready(pcm)) + return -1; + + rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_HWSYNC); + if (rc < 0) + return -1; + + if ((pcm->mmap_status->state != PCM_STATE_RUNNING) && + (pcm->mmap_status->state != PCM_STATE_DRAINING)) + return -1; + + *tstamp = pcm->mmap_status->tstamp; + if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0) + return -1; + + hw_ptr = pcm->mmap_status->hw_ptr; + if (pcm->flags & PCM_IN) + frames = hw_ptr - pcm->mmap_control->appl_ptr; + else + frames = hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr; + + if (frames < 0) + frames += pcm->boundary; + else if (frames > (int)pcm->boundary) + frames -= pcm->boundary; + + *avail = (unsigned int)frames; + + return 0; +} + +int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr, struct timespec *tstamp) +{ + int frames; + int rc; + + if (pcm == NULL || hw_ptr == NULL || tstamp == NULL) + return oops(pcm, EINVAL, "pcm %p, hw_ptr %p, tstamp %p", pcm, hw_ptr, tstamp); + + if (!pcm_is_ready(pcm)) + return oops(pcm, errno, "pcm_is_ready failed"); + + rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC); + if (rc < 0) + return oops(pcm, errno, "pcm_sync_ptr failed"); + + if (pcm->mmap_status == NULL) + return oops(pcm, EINVAL, "pcm %p, mmap_status is NULL", pcm); + + if ((pcm->mmap_status->state != PCM_STATE_RUNNING) && + (pcm->mmap_status->state != PCM_STATE_DRAINING)) + return oops(pcm, ENOSYS, "invalid stream state %d", pcm->mmap_status->state); + + *tstamp = pcm->mmap_status->tstamp; + if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0) + return oops(pcm, errno, "invalid time stamp"); + + *hw_ptr = pcm->mmap_status->hw_ptr; + + return 0; +} + +int pcm_write(struct pcm *pcm, const void *data, unsigned int count) +{ + struct snd_xferi x; + + if (pcm->flags & PCM_IN) + return -EINVAL; + + x.buf = (void*)data; + x.frames = count / (pcm->config.channels * + pcm_format_to_bits(pcm->config.format) / 8); + + for (;;) { + if (!pcm->running) { + int prepare_error = pcm_prepare(pcm); + if (prepare_error) + return prepare_error; + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) + return oops(pcm, errno, "cannot write initial data"); + pcm->running = 1; + return 0; + } + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) { + pcm->prepared = 0; + pcm->running = 0; + if (errno == EPIPE) { + /* we failed to make our window -- try to restart if we are + * allowed to do so. Otherwise, simply allow the EPIPE error to + * propagate up to the app level */ + pcm->underruns++; + if (pcm->flags & PCM_NORESTART) + return -EPIPE; + continue; + } + return oops(pcm, errno, "cannot write stream data"); + } + return 0; + } +} + +int pcm_read(struct pcm *pcm, void *data, unsigned int count) +{ + struct snd_xferi x; + + if (!(pcm->flags & PCM_IN)) + return -EINVAL; + + x.buf = data; + x.frames = count / (pcm->config.channels * + pcm_format_to_bits(pcm->config.format) / 8); + + for (;;) { + if (!pcm->running) { + if (pcm_start(pcm) < 0) { + fprintf(stderr, "start error"); + return -errno; + } + } + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) { + pcm->prepared = 0; + pcm->running = 0; + if (errno == EPIPE) { + /* we failed to make our window -- try to restart */ + pcm->underruns++; + continue; + } + return oops(pcm, errno, "cannot read stream data"); + } + return 0; + } +} + +static struct pcm bad_pcm = { + .fd = -1, +}; + +struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, + unsigned int flags) +{ + struct snd_pcm_hw_params *params; + char fn[256]; + int fd; + + snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, + flags & PCM_IN ? 'c' : 'p'); + + fd = open(fn, O_RDWR); + if (fd < 0) { + fprintf(stderr, "cannot open device '%s'\n", fn); + goto err_open; + } + + params = calloc(1, sizeof(struct snd_pcm_hw_params)); + if (!params) + goto err_calloc; + + param_init(params); + if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) { + fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno); + goto err_hw_refine; + } + + close(fd); + + return (struct pcm_params *)params; + +err_hw_refine: + free(params); +err_calloc: + close(fd); +err_open: + return NULL; +} + +void pcm_params_free(struct pcm_params *pcm_params) +{ + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + + if (params) + free(params); +} + +static int pcm_param_to_alsa(enum pcm_param param) +{ + switch (param) { + case PCM_PARAM_ACCESS: + return SNDRV_PCM_HW_PARAM_ACCESS; + case PCM_PARAM_FORMAT: + return SNDRV_PCM_HW_PARAM_FORMAT; + case PCM_PARAM_SUBFORMAT: + return SNDRV_PCM_HW_PARAM_SUBFORMAT; + case PCM_PARAM_SAMPLE_BITS: + return SNDRV_PCM_HW_PARAM_SAMPLE_BITS; + break; + case PCM_PARAM_FRAME_BITS: + return SNDRV_PCM_HW_PARAM_FRAME_BITS; + break; + case PCM_PARAM_CHANNELS: + return SNDRV_PCM_HW_PARAM_CHANNELS; + break; + case PCM_PARAM_RATE: + return SNDRV_PCM_HW_PARAM_RATE; + break; + case PCM_PARAM_PERIOD_TIME: + return SNDRV_PCM_HW_PARAM_PERIOD_TIME; + break; + case PCM_PARAM_PERIOD_SIZE: + return SNDRV_PCM_HW_PARAM_PERIOD_SIZE; + break; + case PCM_PARAM_PERIOD_BYTES: + return SNDRV_PCM_HW_PARAM_PERIOD_BYTES; + break; + case PCM_PARAM_PERIODS: + return SNDRV_PCM_HW_PARAM_PERIODS; + break; + case PCM_PARAM_BUFFER_TIME: + return SNDRV_PCM_HW_PARAM_BUFFER_TIME; + break; + case PCM_PARAM_BUFFER_SIZE: + return SNDRV_PCM_HW_PARAM_BUFFER_SIZE; + break; + case PCM_PARAM_BUFFER_BYTES: + return SNDRV_PCM_HW_PARAM_BUFFER_BYTES; + break; + case PCM_PARAM_TICK_TIME: + return SNDRV_PCM_HW_PARAM_TICK_TIME; + break; + + default: + return -1; + } +} + +struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params, + enum pcm_param param) +{ + int p; + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + if (params == NULL) { + return NULL; + } + + p = pcm_param_to_alsa(param); + if (p < 0 || !param_is_mask(p)) { + return NULL; + } + + return (struct pcm_mask *)param_to_mask(params, p); +} + +unsigned int pcm_params_get_min(struct pcm_params *pcm_params, + enum pcm_param param) +{ + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + int p; + + if (!params) + return 0; + + p = pcm_param_to_alsa(param); + if (p < 0) + return 0; + + return param_get_min(params, p); +} + +void pcm_params_set_min(struct pcm_params *pcm_params, + enum pcm_param param, unsigned int val) +{ + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + int p; + + if (!params) + return; + + p = pcm_param_to_alsa(param); + if (p < 0) + return; + + param_set_min(params, p, val); +} + +unsigned int pcm_params_get_max(struct pcm_params *pcm_params, + enum pcm_param param) +{ + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + int p; + + if (!params) + return 0; + + p = pcm_param_to_alsa(param); + if (p < 0) + return 0; + + return param_get_max(params, p); +} + +void pcm_params_set_max(struct pcm_params *pcm_params, + enum pcm_param param, unsigned int val) +{ + struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; + int p; + + if (!params) + return; + + p = pcm_param_to_alsa(param); + if (p < 0) + return; + + param_set_max(params, p, val); +} + +static int pcm_mask_test(struct pcm_mask *m, unsigned int index) +{ + const unsigned int bitshift = 5; /* for 32 bit integer */ + const unsigned int bitmask = (1 << bitshift) - 1; + unsigned int element; + + element = index >> bitshift; + if (element >= ARRAY_SIZE(m->bits)) + return 0; /* for safety, but should never occur */ + return (m->bits[element] >> (index & bitmask)) & 1; +} + +static int pcm_mask_to_string(struct pcm_mask *m, char *string, unsigned int size, + char *mask_name, + const char * const *bit_array_name, size_t bit_array_size) +{ + unsigned int i; + unsigned int offset = 0; + + if (m == NULL) + return 0; + if (bit_array_size < 32) { + STRLOG(string, offset, size, "%12s:\t%#08x\n", mask_name, m->bits[0]); + } else { /* spans two or more bitfields, print with an array index */ + for (i = 0; i < (bit_array_size + 31) >> 5; ++i) { + STRLOG(string, offset, size, "%9s[%d]:\t%#08x\n", + mask_name, i, m->bits[i]); + } + } + for (i = 0; i < bit_array_size; ++i) { + if (pcm_mask_test(m, i)) { + STRLOG(string, offset, size, "%12s \t%s\n", "", bit_array_name[i]); + } + } + return offset; +} + +int pcm_params_to_string(struct pcm_params *params, char *string, unsigned int size) +{ + struct pcm_mask *m; + unsigned int min, max; + unsigned int clipoffset, offset; + + m = pcm_params_get_mask(params, PCM_PARAM_ACCESS); + offset = pcm_mask_to_string(m, string, size, + "Access", access_lookup, ARRAY_SIZE(access_lookup)); + m = pcm_params_get_mask(params, PCM_PARAM_FORMAT); + clipoffset = offset > size ? size : offset; + offset += pcm_mask_to_string(m, string + clipoffset, size - clipoffset, + "Format", format_lookup, ARRAY_SIZE(format_lookup)); + m = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT); + clipoffset = offset > size ? size : offset; + offset += pcm_mask_to_string(m, string + clipoffset, size - clipoffset, + "Subformat", subformat_lookup, ARRAY_SIZE(subformat_lookup)); + min = pcm_params_get_min(params, PCM_PARAM_RATE); + max = pcm_params_get_max(params, PCM_PARAM_RATE); + STRLOG(string, offset, size, " Rate:\tmin=%uHz\tmax=%uHz\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_CHANNELS); + max = pcm_params_get_max(params, PCM_PARAM_CHANNELS); + STRLOG(string, offset, size, " Channels:\tmin=%u\t\tmax=%u\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS); + max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS); + STRLOG(string, offset, size, " Sample bits:\tmin=%u\t\tmax=%u\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE); + max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE); + STRLOG(string, offset, size, " Period size:\tmin=%u\t\tmax=%u\n", min, max); + min = pcm_params_get_min(params, PCM_PARAM_PERIODS); + max = pcm_params_get_max(params, PCM_PARAM_PERIODS); + STRLOG(string, offset, size, "Period count:\tmin=%u\t\tmax=%u\n", min, max); + return offset; +} + +int pcm_params_format_test(struct pcm_params *params, enum pcm_format format) +{ + unsigned int alsa_format = pcm_format_to_alsa(format); + + if (alsa_format == SNDRV_PCM_FORMAT_S16_LE && format != PCM_FORMAT_S16_LE) + return 0; /* caution: format not recognized is equivalent to S16_LE */ + return pcm_mask_test(pcm_params_get_mask(params, PCM_PARAM_FORMAT), alsa_format); +} + +int pcm_close(struct pcm *pcm) +{ + if (pcm == &bad_pcm) + return 0; + + pcm_hw_munmap_status(pcm); + + if (pcm->flags & PCM_MMAP) { + pcm_stop(pcm); + munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); + } + + if (pcm->fd >= 0) + close(pcm->fd); + pcm->prepared = 0; + pcm->running = 0; + pcm->buffer_size = 0; + pcm->fd = -1; + free(pcm); + return 0; +} + +struct pcm *pcm_open(unsigned int card, unsigned int device, + unsigned int flags, struct pcm_config *config) +{ + struct pcm *pcm; + struct snd_pcm_info info; + struct snd_pcm_hw_params params; + struct snd_pcm_sw_params sparams; + char fn[256]; + int rc; + + pcm = calloc(1, sizeof(struct pcm)); + if (!pcm || !config) + return &bad_pcm; /* TODO: could support default config here */ + + pcm->config = *config; + + snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, + flags & PCM_IN ? 'c' : 'p'); + + pcm->flags = flags; + pcm->fd = open(fn, O_RDWR|O_NONBLOCK); + if (pcm->fd < 0) { + oops(pcm, errno, "cannot open device '%s'", fn); + return pcm; + } + + if (fcntl(pcm->fd, F_SETFL, fcntl(pcm->fd, F_GETFL) & + ~O_NONBLOCK) < 0) { + oops(pcm, errno, "failed to reset blocking mode '%s'", fn); + goto fail_close; + } + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { + oops(pcm, errno, "cannot get info"); + goto fail_close; + } + pcm->subdevice = info.subdevice; + + param_init(¶ms); + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT, + pcm_format_to_alsa(config->format)); + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT, + SNDRV_PCM_SUBFORMAT_STD); + param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + pcm_format_to_bits(config->format)); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS, + pcm_format_to_bits(config->format) * config->channels); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS, + config->channels); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count); + param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate); + param_set_flag(¶ms, config->flag); + + if (flags & PCM_NOIRQ) { + if (!(flags & PCM_MMAP)) { + oops(pcm, EINVAL, "noirq only currently supported with mmap()."); + goto fail_close; + } + + params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP; + pcm->noirq_frames_per_msec = config->rate / 1000; + } + + if (flags & PCM_MMAP) + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, + SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, + SNDRV_PCM_ACCESS_RW_INTERLEAVED); + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { + oops(pcm, errno, "cannot set hw params"); + goto fail_close; + } + + /* get our refined hw_params */ + config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS); + pcm->buffer_size = config->period_count * config->period_size; + + if (flags & PCM_MMAP) { + pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size), + PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0); + if (pcm->mmap_buffer == MAP_FAILED) { + oops(pcm, errno, "failed to mmap buffer %d bytes\n", + pcm_frames_to_bytes(pcm, pcm->buffer_size)); + goto fail_close; + } + } + + memset(&sparams, 0, sizeof(sparams)); + sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE; + sparams.period_step = 1; + + if (!config->start_threshold) { + if (pcm->flags & PCM_IN) + pcm->config.start_threshold = sparams.start_threshold = 1; + else + pcm->config.start_threshold = sparams.start_threshold = + config->period_count * config->period_size / 2; + } else + sparams.start_threshold = config->start_threshold; + + /* pick a high stop threshold - todo: does this need further tuning */ + if (!config->stop_threshold) { + if (pcm->flags & PCM_IN) + pcm->config.stop_threshold = sparams.stop_threshold = + config->period_count * config->period_size * 10; + else + pcm->config.stop_threshold = sparams.stop_threshold = + config->period_count * config->period_size; + } + else + sparams.stop_threshold = config->stop_threshold; + + if (!pcm->config.avail_min) { + if (pcm->flags & PCM_MMAP) + pcm->config.avail_min = sparams.avail_min = pcm->config.period_size; + else + pcm->config.avail_min = sparams.avail_min = 1; + } else + sparams.avail_min = config->avail_min; + + sparams.xfer_align = config->period_size / 2; /* needed for old kernels */ + sparams.silence_threshold = config->silence_threshold; + sparams.silence_size = config->silence_size; + pcm->boundary = sparams.boundary = pcm->buffer_size; + + while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size) + pcm->boundary *= 2; + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) { + oops(pcm, errno, "cannot set sw params"); + goto fail; + } + + rc = pcm_hw_mmap_status(pcm); + if (rc < 0) { + oops(pcm, errno, "mmap status failed"); + goto fail; + } + +#ifdef SNDRV_PCM_IOCTL_TTSTAMP + if (pcm->flags & PCM_MONOTONIC) { + int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC; + rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg); + if (rc < 0) { + oops(pcm, errno, "cannot set timestamp type"); + goto fail; + } + } +#endif + + pcm->underruns = 0; + return pcm; + +fail: + if (flags & PCM_MMAP) + munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); +fail_close: + close(pcm->fd); + pcm->fd = -1; + return pcm; +} + +int pcm_is_ready(struct pcm *pcm) +{ + return pcm->fd >= 0; +} + +int pcm_prepare(struct pcm *pcm) +{ + if (pcm->prepared) + return 0; + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0) + return oops(pcm, errno, "cannot prepare channel"); + + pcm->prepared = 1; + return 0; +} + +int pcm_start(struct pcm *pcm) +{ + int prepare_error = pcm_prepare(pcm); + if (prepare_error) + return prepare_error; + + if (pcm->flags & PCM_MMAP) + pcm_sync_ptr(pcm, 0); + + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0) + return oops(pcm, errno, "cannot start channel"); + + pcm->running = 1; + return 0; +} + +int pcm_stop(struct pcm *pcm) +{ + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0) + return oops(pcm, errno, "cannot stop channel"); + + pcm->prepared = 0; + pcm->running = 0; + return 0; +} + +static inline int pcm_mmap_playback_avail(struct pcm *pcm) +{ + int avail; + + avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr; + + if (avail < 0) + avail += pcm->boundary; + else if (avail > (int)pcm->boundary) + avail -= pcm->boundary; + + return avail; +} + +static inline int pcm_mmap_capture_avail(struct pcm *pcm) +{ + int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr; + if (avail < 0) + avail += pcm->boundary; + return avail; +} + +int pcm_mmap_avail(struct pcm *pcm) +{ + pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC); + if (pcm->flags & PCM_IN) + return pcm_mmap_capture_avail(pcm); + else + return pcm_mmap_playback_avail(pcm); +} + +static void pcm_mmap_appl_forward(struct pcm *pcm, int frames) +{ + unsigned int appl_ptr = pcm->mmap_control->appl_ptr; + appl_ptr += frames; + + /* check for boundary wrap */ + if (appl_ptr > pcm->boundary) + appl_ptr -= pcm->boundary; + pcm->mmap_control->appl_ptr = appl_ptr; +} + +int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, + unsigned int *frames) +{ + unsigned int continuous, copy_frames, avail; + + /* return the mmap buffer */ + *areas = pcm->mmap_buffer; + + /* and the application offset in frames */ + *offset = pcm->mmap_control->appl_ptr % pcm->buffer_size; + + avail = pcm_mmap_avail(pcm); + if (avail > pcm->buffer_size) + avail = pcm->buffer_size; + continuous = pcm->buffer_size - *offset; + + /* we can only copy frames if the are availabale and continuos */ + copy_frames = *frames; + if (copy_frames > avail) + copy_frames = avail; + if (copy_frames > continuous) + copy_frames = continuous; + *frames = copy_frames; + + return 0; +} + +int pcm_mmap_commit(struct pcm *pcm, unsigned int offset __attribute__((unused)), unsigned int frames) +{ + /* update the application pointer in userspace and kernel */ + pcm_mmap_appl_forward(pcm, frames); + pcm_sync_ptr(pcm, 0); + + return frames; +} + +int pcm_avail_update(struct pcm *pcm) +{ + pcm_sync_ptr(pcm, 0); + return pcm_mmap_avail(pcm); +} + +int pcm_state(struct pcm *pcm) +{ + int err = pcm_sync_ptr(pcm, 0); + if (err < 0) + return err; + + return pcm->mmap_status->state; +} + +int pcm_set_avail_min(struct pcm *pcm, int avail_min) +{ + if ((~pcm->flags) & (PCM_MMAP | PCM_NOIRQ)) + return -ENOSYS; + + pcm->config.avail_min = avail_min; + return 0; +} + +int pcm_wait(struct pcm *pcm, int timeout) +{ + struct pollfd pfd; + int err; + + pfd.fd = pcm->fd; + pfd.events = POLLOUT | POLLERR | POLLNVAL; + + do { + /* let's wait for avail or timeout */ + err = poll(&pfd, 1, timeout); + if (err < 0) + return -errno; + + /* timeout ? */ + if (err == 0) + return 0; + + /* have we been interrupted ? */ + if (errno == -EINTR) + continue; + + /* check for any errors */ + if (pfd.revents & (POLLERR | POLLNVAL)) { + switch (pcm_state(pcm)) { + case PCM_STATE_XRUN: + return -EPIPE; + case PCM_STATE_SUSPENDED: + return -ESTRPIPE; + case PCM_STATE_DISCONNECTED: + return -ENODEV; + default: + return -EIO; + } + } + /* poll again if fd not ready for IO */ + } while (!(pfd.revents & (POLLIN | POLLOUT))); + + return 1; +} + +int pcm_get_poll_fd(struct pcm *pcm) +{ + return pcm->fd; +} + +int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes) +{ + int err = 0, frames, avail; + unsigned int offset = 0, count; + + if (bytes == 0) + return 0; + + count = pcm_bytes_to_frames(pcm, bytes); + + while (count > 0) { + + /* get the available space for writing new frames */ + avail = pcm_avail_update(pcm); + if (avail < 0) { + fprintf(stderr, "cannot determine available mmap frames"); + return err; + } + + /* start the audio if we reach the threshold */ + if (!pcm->running && + (pcm->buffer_size - avail) >= pcm->config.start_threshold) { + if (pcm_start(pcm) < 0) { + fprintf(stderr, "start error: hw 0x%x app 0x%x avail 0x%x\n", + (unsigned int)pcm->mmap_status->hw_ptr, + (unsigned int)pcm->mmap_control->appl_ptr, + avail); + return -errno; + } + pcm->wait_for_avail_min = 0; + } + + /* sleep until we have space to write new frames */ + if (pcm->running) { + /* enable waiting for avail_min threshold when less frames than we have to write + * are available. */ + if (!pcm->wait_for_avail_min && (count > (unsigned int)avail)) + pcm->wait_for_avail_min = 1; + + if (pcm->wait_for_avail_min && (avail < pcm->config.avail_min)) { + int time = -1; + + /* disable waiting for avail_min threshold to allow small amounts of data to be + * written without waiting as long as there is enough room in buffer. */ + pcm->wait_for_avail_min = 0; + + if (pcm->flags & PCM_NOIRQ) + time = (pcm->config.avail_min - avail) / pcm->noirq_frames_per_msec; + + err = pcm_wait(pcm, time); + if (err < 0) { + pcm->prepared = 0; + pcm->running = 0; + oops(pcm, errno, "wait error: hw 0x%x app 0x%x avail 0x%x\n", + (unsigned int)pcm->mmap_status->hw_ptr, + (unsigned int)pcm->mmap_control->appl_ptr, + avail); + pcm->mmap_control->appl_ptr = 0; + return err; + } + continue; + } + } + + frames = count; + if (frames > avail) + frames = avail; + + if (!frames) + break; + + /* copy frames from buffer */ + frames = pcm_mmap_transfer_areas(pcm, (void *)buffer, offset, frames); + if (frames < 0) { + fprintf(stderr, "write error: hw 0x%x app 0x%x avail 0x%x\n", + (unsigned int)pcm->mmap_status->hw_ptr, + (unsigned int)pcm->mmap_control->appl_ptr, + avail); + return frames; + } + + offset += frames; + count -= frames; + } + + return 0; +} + +int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count) +{ + if ((~pcm->flags) & (PCM_OUT | PCM_MMAP)) + return -ENOSYS; + + return pcm_mmap_transfer(pcm, (void *)data, count); +} + +int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count) +{ + if ((~pcm->flags) & (PCM_IN | PCM_MMAP)) + return -ENOSYS; + + return pcm_mmap_transfer(pcm, data, count); +} + +int pcm_ioctl(struct pcm *pcm, int request, ...) +{ + va_list ap; + void * arg; + + if (!pcm_is_ready(pcm)) + return -1; + + va_start(ap, request); + arg = va_arg(ap, void *); + va_end(ap); + + return ioctl(pcm->fd, request, arg); +} diff --git a/app/src/main/cpp/sha1sum.cpp b/app/src/main/cpp/sha1sum.cpp new file mode 100644 index 0000000..503891d --- /dev/null +++ b/app/src/main/cpp/sha1sum.cpp @@ -0,0 +1,365 @@ +// +// Created by 서인석 on 2020-10-22. +// + +/* sha1sum.c - print SHA-1 Message-Digest Algorithm + * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + * Copyright (C) 2004 g10 Code GmbH + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* SHA-1 coden take from gnupg 1.3.92. + + Note, that this is a simple tool to be used for MS Windows. +*/ + +#include +#include +#include +#include +#include + +#undef BIG_ENDIAN_HOST +typedef unsigned int u32; + +/**************** + * Rotate a 32 bit integer by n bytes + */ +#if defined(__GNUC__) && defined(__i386__) +static inline u32 +rol( u32 x, int n) +{ + __asm__("roll %%cl,%0" + :"=r" (x) + :"0" (x),"c" (n)); + return x; +} +#else +#define rol(x,n) ( ((x) << (n)) | ((x) >> (32-(n))) ) +#endif + + +typedef struct { + u32 h0,h1,h2,h3,h4; + u32 nblocks; + unsigned char buf[64]; + int count; +} SHA1_CONTEXT; + + + +void +sha1_init( SHA1_CONTEXT *hd ) +{ + hd->h0 = 0x67452301; + hd->h1 = 0xefcdab89; + hd->h2 = 0x98badcfe; + hd->h3 = 0x10325476; + hd->h4 = 0xc3d2e1f0; + hd->nblocks = 0; + hd->count = 0; +} + + +/**************** + * Transform the message X which consists of 16 32-bit-words + */ +static void +transform( SHA1_CONTEXT *hd, unsigned char *data ) +{ + u32 a,b,c,d,e,tm; + u32 x[16]; + + /* get values from the chaining vars */ + a = hd->h0; + b = hd->h1; + c = hd->h2; + d = hd->h3; + e = hd->h4; + +#ifdef BIG_ENDIAN_HOST + memcpy( x, data, 64 ); +#else + { int i; + unsigned char *p2; + for(i=0, p2=(unsigned char*)x; i < 16; i++, p2 += 4 ) { + p2[3] = *data++; + p2[2] = *data++; + p2[1] = *data++; + p2[0] = *data++; + } + } +#endif + + +#define K1 0x5A827999L +#define K2 0x6ED9EBA1L +#define K3 0x8F1BBCDCL +#define K4 0xCA62C1D6L +#define F1(x,y,z) ( z ^ ( x & ( y ^ z ) ) ) +#define F2(x,y,z) ( x ^ y ^ z ) +#define F3(x,y,z) ( ( x & y ) | ( z & ( x | y ) ) ) +#define F4(x,y,z) ( x ^ y ^ z ) + + +#define M(i) ( tm = x[i&0x0f] ^ x[(i-14)&0x0f] \ + ^ x[(i-8)&0x0f] ^ x[(i-3)&0x0f] \ + , (x[i&0x0f] = rol(tm,1)) ) + +#define R(a,b,c,d,e,f,k,m) do { e += rol( a, 5 ) \ + + f( b, c, d ) \ + + k \ + + m; \ + b = rol( b, 30 ); \ + } while(0) + R( a, b, c, d, e, F1, K1, x[ 0] ); + R( e, a, b, c, d, F1, K1, x[ 1] ); + R( d, e, a, b, c, F1, K1, x[ 2] ); + R( c, d, e, a, b, F1, K1, x[ 3] ); + R( b, c, d, e, a, F1, K1, x[ 4] ); + R( a, b, c, d, e, F1, K1, x[ 5] ); + R( e, a, b, c, d, F1, K1, x[ 6] ); + R( d, e, a, b, c, F1, K1, x[ 7] ); + R( c, d, e, a, b, F1, K1, x[ 8] ); + R( b, c, d, e, a, F1, K1, x[ 9] ); + R( a, b, c, d, e, F1, K1, x[10] ); + R( e, a, b, c, d, F1, K1, x[11] ); + R( d, e, a, b, c, F1, K1, x[12] ); + R( c, d, e, a, b, F1, K1, x[13] ); + R( b, c, d, e, a, F1, K1, x[14] ); + R( a, b, c, d, e, F1, K1, x[15] ); + R( e, a, b, c, d, F1, K1, M(16) ); + R( d, e, a, b, c, F1, K1, M(17) ); + R( c, d, e, a, b, F1, K1, M(18) ); + R( b, c, d, e, a, F1, K1, M(19) ); + R( a, b, c, d, e, F2, K2, M(20) ); + R( e, a, b, c, d, F2, K2, M(21) ); + R( d, e, a, b, c, F2, K2, M(22) ); + R( c, d, e, a, b, F2, K2, M(23) ); + R( b, c, d, e, a, F2, K2, M(24) ); + R( a, b, c, d, e, F2, K2, M(25) ); + R( e, a, b, c, d, F2, K2, M(26) ); + R( d, e, a, b, c, F2, K2, M(27) ); + R( c, d, e, a, b, F2, K2, M(28) ); + R( b, c, d, e, a, F2, K2, M(29) ); + R( a, b, c, d, e, F2, K2, M(30) ); + R( e, a, b, c, d, F2, K2, M(31) ); + R( d, e, a, b, c, F2, K2, M(32) ); + R( c, d, e, a, b, F2, K2, M(33) ); + R( b, c, d, e, a, F2, K2, M(34) ); + R( a, b, c, d, e, F2, K2, M(35) ); + R( e, a, b, c, d, F2, K2, M(36) ); + R( d, e, a, b, c, F2, K2, M(37) ); + R( c, d, e, a, b, F2, K2, M(38) ); + R( b, c, d, e, a, F2, K2, M(39) ); + R( a, b, c, d, e, F3, K3, M(40) ); + R( e, a, b, c, d, F3, K3, M(41) ); + R( d, e, a, b, c, F3, K3, M(42) ); + R( c, d, e, a, b, F3, K3, M(43) ); + R( b, c, d, e, a, F3, K3, M(44) ); + R( a, b, c, d, e, F3, K3, M(45) ); + R( e, a, b, c, d, F3, K3, M(46) ); + R( d, e, a, b, c, F3, K3, M(47) ); + R( c, d, e, a, b, F3, K3, M(48) ); + R( b, c, d, e, a, F3, K3, M(49) ); + R( a, b, c, d, e, F3, K3, M(50) ); + R( e, a, b, c, d, F3, K3, M(51) ); + R( d, e, a, b, c, F3, K3, M(52) ); + R( c, d, e, a, b, F3, K3, M(53) ); + R( b, c, d, e, a, F3, K3, M(54) ); + R( a, b, c, d, e, F3, K3, M(55) ); + R( e, a, b, c, d, F3, K3, M(56) ); + R( d, e, a, b, c, F3, K3, M(57) ); + R( c, d, e, a, b, F3, K3, M(58) ); + R( b, c, d, e, a, F3, K3, M(59) ); + R( a, b, c, d, e, F4, K4, M(60) ); + R( e, a, b, c, d, F4, K4, M(61) ); + R( d, e, a, b, c, F4, K4, M(62) ); + R( c, d, e, a, b, F4, K4, M(63) ); + R( b, c, d, e, a, F4, K4, M(64) ); + R( a, b, c, d, e, F4, K4, M(65) ); + R( e, a, b, c, d, F4, K4, M(66) ); + R( d, e, a, b, c, F4, K4, M(67) ); + R( c, d, e, a, b, F4, K4, M(68) ); + R( b, c, d, e, a, F4, K4, M(69) ); + R( a, b, c, d, e, F4, K4, M(70) ); + R( e, a, b, c, d, F4, K4, M(71) ); + R( d, e, a, b, c, F4, K4, M(72) ); + R( c, d, e, a, b, F4, K4, M(73) ); + R( b, c, d, e, a, F4, K4, M(74) ); + R( a, b, c, d, e, F4, K4, M(75) ); + R( e, a, b, c, d, F4, K4, M(76) ); + R( d, e, a, b, c, F4, K4, M(77) ); + R( c, d, e, a, b, F4, K4, M(78) ); + R( b, c, d, e, a, F4, K4, M(79) ); + + /* Update chaining vars */ + hd->h0 += a; + hd->h1 += b; + hd->h2 += c; + hd->h3 += d; + hd->h4 += e; +} + + +/* Update the message digest with the contents + * of INBUF with length INLEN. + */ +static void +sha1_write( SHA1_CONTEXT *hd, unsigned char *inbuf, size_t inlen) +{ + if( hd->count == 64 ) { /* flush the buffer */ + transform( hd, hd->buf ); + hd->count = 0; + hd->nblocks++; + } + if( !inbuf ) + return; + if( hd->count ) { + for( ; inlen && hd->count < 64; inlen-- ) + hd->buf[hd->count++] = *inbuf++; + sha1_write( hd, NULL, 0 ); + if( !inlen ) + return; + } + + while( inlen >= 64 ) { + transform( hd, inbuf ); + hd->count = 0; + hd->nblocks++; + inlen -= 64; + inbuf += 64; + } + for( ; inlen && hd->count < 64; inlen-- ) + hd->buf[hd->count++] = *inbuf++; +} + + +/* The routine final terminates the computation and + * returns the digest. + * The handle is prepared for a new cycle, but adding bytes to the + * handle will the destroy the returned buffer. + * Returns: 20 bytes representing the digest. + */ + +static void +sha1_final(SHA1_CONTEXT *hd) +{ + u32 t, msb, lsb; + unsigned char *p; + + sha1_write(hd, NULL, 0); /* flush */; + + t = hd->nblocks; + /* multiply by 64 to make a byte count */ + lsb = t << 6; + msb = t >> 26; + /* add the count */ + t = lsb; + if( (lsb += hd->count) < t ) + msb++; + /* multiply by 8 to make a bit count */ + t = lsb; + lsb <<= 3; + msb <<= 3; + msb |= t >> 29; + + if( hd->count < 56 ) { /* enough room */ + hd->buf[hd->count++] = 0x80; /* pad */ + while( hd->count < 56 ) + hd->buf[hd->count++] = 0; /* pad */ + } + else { /* need one extra block */ + hd->buf[hd->count++] = 0x80; /* pad character */ + while( hd->count < 64 ) + hd->buf[hd->count++] = 0; + sha1_write(hd, NULL, 0); /* flush */; + memset(hd->buf, 0, 56 ); /* fill next block with zeroes */ + } + /* append the 64 bit count */ + hd->buf[56] = msb >> 24; + hd->buf[57] = msb >> 16; + hd->buf[58] = msb >> 8; + hd->buf[59] = msb ; + hd->buf[60] = lsb >> 24; + hd->buf[61] = lsb >> 16; + hd->buf[62] = lsb >> 8; + hd->buf[63] = lsb ; + transform( hd, hd->buf ); + + p = hd->buf; +#ifdef BIG_ENDIAN_HOST +#define X(a) do { *(u32*)p = hd->h##a ; p += 4; } while(0) +#else /* little endian */ +#define X(a) do { *p++ = hd->h##a >> 24; *p++ = hd->h##a >> 16; \ + *p++ = hd->h##a >> 8; *p++ = hd->h##a; } while(0) +#endif + X(0); + X(1); + X(2); + X(3); + X(4); +#undef X +} + + +int rito_cksum (char *pFilePath, char *pResult) +{ + FILE *fp; + char buffer[4096]; + size_t n; + size_t file_size = 0; + size_t jump = 0; + SHA1_CONTEXT ctx; + int i; + + fp = fopen(pFilePath, "rb"); + if (!fp) { + fprintf(stderr, "can't open `%s': %s\n", pFilePath, strerror(errno)); + return -1; + } + + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + jump = file_size / (4 * 1024 * 1024) * sizeof(buffer); + fseek(fp, 0, SEEK_SET); + sha1_init(&ctx); + + while ((n = fread(buffer, 1, sizeof buffer, fp))) { + sha1_write(&ctx, reinterpret_cast(buffer), n); + fseek(fp, jump, SEEK_CUR); + } + if (ferror(fp)) { + fprintf(stderr, "error reading `%s': %s\n", pFilePath, strerror(errno)); + fclose(fp); + return -2; + } + sha1_final(&ctx); + fclose(fp); + + if (pResult) { + for (i = 0; i < 20; i++) + sprintf(pResult + strlen(pResult), "%02x", ctx.buf[i]); + } + + return 0; +} + +/* +Local Variables: +compile-command: "cc -Wall -g -o sha1sum sha1sum.c" +End: +*/ \ No newline at end of file diff --git a/app/src/main/cpp/sha1sum.h b/app/src/main/cpp/sha1sum.h new file mode 100644 index 0000000..d4288de --- /dev/null +++ b/app/src/main/cpp/sha1sum.h @@ -0,0 +1,10 @@ +// +// Created by 서인석 on 2020-10-22. +// + +#ifndef OSICMANAGER_SHA1SUM_H +#define OSICMANAGER_SHA1SUM_H + +int rito_cksum (char *pFilePath, char *pResult); + +#endif //OSICMANAGER_SHA1SUM_H diff --git a/app/src/main/cpp/tinyalsa/asoundlib.h b/app/src/main/cpp/tinyalsa/asoundlib.h new file mode 100644 index 0000000..9babb23 --- /dev/null +++ b/app/src/main/cpp/tinyalsa/asoundlib.h @@ -0,0 +1,325 @@ +/* asoundlib.h +** +** Copyright 2011, The Android Open Source Project +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +** DAMAGE. +*/ + +#ifndef ASOUNDLIB_H +#define ASOUNDLIB_H + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +/* + * PCM API + */ + +struct pcm; + +#define PCM_OUT 0x00000000 +#define PCM_IN 0x10000000 +#define PCM_MMAP 0x00000001 +#define PCM_NOIRQ 0x00000002 +#define PCM_NORESTART 0x00000004 /* PCM_NORESTART - when set, calls to + * pcm_write for a playback stream will not + * attempt to restart the stream in the case + * of an underflow, but will return -EPIPE + * instead. After the first -EPIPE error, the + * stream is considered to be stopped, and a + * second call to pcm_write will attempt to + * restart the stream. + */ +#define PCM_MONOTONIC 0x00000008 /* see pcm_get_htimestamp */ + +/* PCM runtime states */ +#define PCM_STATE_OPEN 0 +#define PCM_STATE_SETUP 1 +#define PCM_STATE_PREPARED 2 +#define PCM_STATE_RUNNING 3 +#define PCM_STATE_XRUN 4 +#define PCM_STATE_DRAINING 5 +#define PCM_STATE_PAUSED 6 +#define PCM_STATE_SUSPENDED 7 +#define PCM_STATE_DISCONNECTED 8 + +/* TLV header size*/ +#define TLV_HEADER_SIZE (2 * sizeof(unsigned int)) + +/* Bit formats */ +enum pcm_format { + PCM_FORMAT_INVALID = -1, + PCM_FORMAT_S16_LE = 0, /* 16-bit signed */ + PCM_FORMAT_S32_LE, /* 32-bit signed */ + PCM_FORMAT_S8, /* 8-bit signed */ + PCM_FORMAT_S24_LE, /* 24-bits in 4-bytes */ + PCM_FORMAT_S24_3LE, /* 24-bits in 3-bytes */ + + PCM_FORMAT_MAX, +}; + +/* Bitmask has 256 bits (32 bytes) in asound.h */ +struct pcm_mask { + unsigned int bits[32 / sizeof(unsigned int)]; +}; + +/* Configuration for a stream */ +struct pcm_config { + unsigned int channels; + unsigned int rate; + unsigned int period_size; + unsigned int period_count; + enum pcm_format format; + + /* Values to use for the ALSA start, stop and silence thresholds, and + * silence size. Setting any one of these values to 0 will cause the + * default tinyalsa values to be used instead. + * Tinyalsa defaults are as follows. + * + * start_threshold : period_count * period_size + * stop_threshold : period_count * period_size + * silence_threshold : 0 + * silence_size : 0 + */ + unsigned int start_threshold; + unsigned int stop_threshold; + unsigned int silence_threshold; + unsigned int silence_size; + + /* Minimum number of frames available before pcm_mmap_write() will actually + * write into the kernel buffer. Only used if the stream is opened in mmap mode + * (pcm_open() called with PCM_MMAP flag set). Use 0 for default. + */ + int avail_min; + int flag; +}; + +/* PCM parameters */ +enum pcm_param +{ + /* mask parameters */ + PCM_PARAM_ACCESS, + PCM_PARAM_FORMAT, + PCM_PARAM_SUBFORMAT, + /* interval parameters */ + PCM_PARAM_SAMPLE_BITS, + PCM_PARAM_FRAME_BITS, + PCM_PARAM_CHANNELS, + PCM_PARAM_RATE, + PCM_PARAM_PERIOD_TIME, + PCM_PARAM_PERIOD_SIZE, + PCM_PARAM_PERIOD_BYTES, + PCM_PARAM_PERIODS, + PCM_PARAM_BUFFER_TIME, + PCM_PARAM_BUFFER_SIZE, + PCM_PARAM_BUFFER_BYTES, + PCM_PARAM_TICK_TIME, +}; + +/* Mixer control types */ +enum mixer_ctl_type { + MIXER_CTL_TYPE_BOOL, + MIXER_CTL_TYPE_INT, + MIXER_CTL_TYPE_ENUM, + MIXER_CTL_TYPE_BYTE, + MIXER_CTL_TYPE_IEC958, + MIXER_CTL_TYPE_INT64, + MIXER_CTL_TYPE_UNKNOWN, + + MIXER_CTL_TYPE_MAX, +}; + +/* Open and close a stream */ +struct pcm *pcm_open(unsigned int card, unsigned int device, + unsigned int flags, struct pcm_config *config); +int pcm_close(struct pcm *pcm); +int pcm_is_ready(struct pcm *pcm); + +/* Obtain the parameters for a PCM */ +struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, + unsigned int flags); +void pcm_params_free(struct pcm_params *pcm_params); + +struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params, + enum pcm_param param); +unsigned int pcm_params_get_min(struct pcm_params *pcm_params, + enum pcm_param param); +void pcm_params_set_min(struct pcm_params *pcm_params, + enum pcm_param param, unsigned int val); +unsigned int pcm_params_get_max(struct pcm_params *pcm_params, + enum pcm_param param); +void pcm_params_set_max(struct pcm_params *pcm_params, + enum pcm_param param, unsigned int val); + +/* Converts the pcm parameters to a human readable string. + * The string parameter is a caller allocated buffer of size bytes, + * which is then filled up to size - 1 and null terminated, + * if size is greater than zero. + * The return value is the number of bytes copied to string + * (not including null termination) if less than size; otherwise, + * the number of bytes required for the buffer. + */ +int pcm_params_to_string(struct pcm_params *params, char *string, unsigned int size); + +/* Returns 1 if the pcm_format is present (format bit set) in + * the pcm_params structure; 0 otherwise, or upon unrecognized format. + */ +int pcm_params_format_test(struct pcm_params *params, enum pcm_format format); + +/* Set and get config */ +int pcm_get_config(struct pcm *pcm, struct pcm_config *config); +int pcm_set_config(struct pcm *pcm, struct pcm_config *config); + +/* Returns a human readable reason for the last error */ +const char *pcm_get_error(struct pcm *pcm); + +/* Returns the sample size in bits for a PCM format. + * As with ALSA formats, this is the storage size for the format, whereas the + * format represents the number of significant bits. For example, + * PCM_FORMAT_S24_LE uses 32 bits of storage. + */ +unsigned int pcm_format_to_bits(enum pcm_format format); + +/* Returns the buffer size (int frames) that should be used for pcm_write. */ +unsigned int pcm_get_buffer_size(struct pcm *pcm); +unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames); +unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes); + +/* Returns the pcm latency in ms */ +unsigned int pcm_get_latency(struct pcm *pcm); + +/* Returns available frames in pcm buffer and corresponding time stamp. + * The clock is CLOCK_MONOTONIC if flag PCM_MONOTONIC was specified in pcm_open, + * otherwise the clock is CLOCK_REALTIME. + * For an input stream, frames available are frames ready for the + * application to read. + * For an output stream, frames available are the number of empty frames available + * for the application to write. + */ +int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, + struct timespec *tstamp); + +/* Returns the subdevice on which the pcm has been opened */ +unsigned int pcm_get_subdevice(struct pcm *pcm); + +/* Write data to the fifo. + * Will start playback on the first write or on a write that + * occurs after a fifo underrun. + */ +int pcm_write(struct pcm *pcm, const void *data, unsigned int count); +int pcm_read(struct pcm *pcm, void *data, unsigned int count); + +/* + * mmap() support. + */ +int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count); +int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count); +int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, + unsigned int *frames); +int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames); +int pcm_mmap_avail(struct pcm *pcm); + +/* Returns current read/write position in the mmap buffer with associated time stamp. + */ +int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr, struct timespec *tstamp); + +/* Prepare the PCM substream to be triggerable */ +int pcm_prepare(struct pcm *pcm); +/* Start and stop a PCM channel that doesn't transfer data */ +int pcm_start(struct pcm *pcm); +int pcm_stop(struct pcm *pcm); + +/* ioctl function for PCM driver */ +int pcm_ioctl(struct pcm *pcm, int request, ...); + +/* Interrupt driven API */ +int pcm_wait(struct pcm *pcm, int timeout); +int pcm_get_poll_fd(struct pcm *pcm); + +/* Change avail_min after the stream has been opened with no need to stop the stream. + * Only accepted if opened with PCM_MMAP and PCM_NOIRQ flags + */ +int pcm_set_avail_min(struct pcm *pcm, int avail_min); + +/* + * MIXER API + */ + +struct mixer; +struct mixer_ctl; + +/* Open and close a mixer */ +struct mixer *mixer_open(unsigned int card); +void mixer_close(struct mixer *mixer); + +/* Get info about a mixer */ +const char *mixer_get_name(struct mixer *mixer); + +/* Obtain mixer controls */ +unsigned int mixer_get_num_ctls(struct mixer *mixer); +struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id); +struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name); + +/* Get info about mixer controls */ +const char *mixer_ctl_get_name(struct mixer_ctl *ctl); +enum mixer_ctl_type mixer_ctl_get_type(struct mixer_ctl *ctl); +const char *mixer_ctl_get_type_string(struct mixer_ctl *ctl); +unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl); +unsigned int mixer_ctl_get_num_enums(struct mixer_ctl *ctl); +const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl, + unsigned int enum_id); + +/* Some sound cards update their controls due to external events, + * such as HDMI EDID byte data changing when an HDMI cable is + * connected. This API allows the count of elements to be updated. + */ +void mixer_ctl_update(struct mixer_ctl *ctl); + +/* Set and get mixer controls */ +int mixer_ctl_get_percent(struct mixer_ctl *ctl, unsigned int id); +int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent); + +int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id); +int mixer_ctl_is_access_tlv_rw(struct mixer_ctl *ctl); +int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count); +int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value); +int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count); +int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string); + +/* Determine range of integer mixer controls */ +int mixer_ctl_get_range_min(struct mixer_ctl *ctl); +int mixer_ctl_get_range_max(struct mixer_ctl *ctl); + +int mixer_subscribe_events(struct mixer *mixer, int subscribe); +int mixer_wait_event(struct mixer *mixer, int timeout); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif diff --git a/app/src/main/java/kr/co/rito/osicmanager/FileDownloader.java b/app/src/main/java/kr/co/rito/osicmanager/FileDownloader.java new file mode 100644 index 0000000..48472fa --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/FileDownloader.java @@ -0,0 +1,220 @@ +package kr.co.rito.osicmanager; + +import android.content.Context; +import android.util.Log; +import android.widget.Toast; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class FileDownloader extends Thread { + private static String TAG = "FileDownloader"; + int DOWNLOAD_STATUS_PROCESS = 0; + int DOWNLOAD_STATUS_SUCCESS = 1; + int DOWNLOAD_STATUS_FAILED = 2; + private int mDownloadStatus = DOWNLOAD_STATUS_PROCESS; + private String mJsonUrlPath; + private LinkedBlockingQueue mDownList; + private Context mContext; + private boolean mIsAlive = false; + + public class DownInfo { + public String mUrl; + public String mSaveDir; + public String mSaveName; + int mTryCount; + } + + private class CallbackToDownloadFile implements Callback { + + private File directory; + private File fileToBeDownloaded; + + public CallbackToDownloadFile(String directory, String fileName) { + this.directory = new File(directory); + this.fileToBeDownloaded = new File(this.directory.getAbsolutePath() + "/" + fileName); + } + + + @Override + public void onFailure(Call call, IOException e) { + mDownloadStatus = DOWNLOAD_STATUS_FAILED; + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (!this.directory.exists()) { + this.directory.mkdirs(); + } + + if (this.fileToBeDownloaded.exists()) { + this.fileToBeDownloaded.delete(); + } + + try { + this.fileToBeDownloaded.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + + mDownloadStatus = DOWNLOAD_STATUS_FAILED; + return; + } + + InputStream is = response.body().byteStream(); + OutputStream os = new FileOutputStream(this.fileToBeDownloaded); + + final int BUFFER_SIZE = 2048; + byte[] data = new byte[BUFFER_SIZE]; + + int count; + long total = 0; + + while ((count = is.read(data)) != -1) { + total += count; + os.write(data, 0, count); + } + + os.flush(); + os.close(); + is.close(); + + mDownloadStatus = DOWNLOAD_STATUS_SUCCESS; + } + } + + public FileDownloader(Context context) { + mContext = context; + mDownList = new LinkedBlockingQueue(10000); + } + + public void addDownloadTarget(String url, String directory, String filename) { + DownInfo info = new DownInfo(); + info.mUrl = url; + info.mSaveDir = directory; + info.mSaveName = filename; + info.mTryCount = 0; + mDownList.offer(info); + } + + @Override + public void run() { + mIsAlive = true; + while (mIsAlive) { + if (mDownList.size() > 0) { + boolean trouble = false; + DownInfo info = mDownList.poll(); + + try { + File destDirectory = new File(info.mSaveDir); + if (!destDirectory.exists()) { + destDirectory.mkdir(); + } + } catch(Exception e) { + trouble = true; + } + + if(trouble) + continue; + + boolean allowUntrusted = true; + OkHttpClient client = new OkHttpClient(); + OkHttpClient.Builder clientBuilder = client.newBuilder().readTimeout(10, TimeUnit.SECONDS); + if (allowUntrusted) { + final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + X509Certificate[] cArrr = new X509Certificate[0]; + return cArrr; + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, + final String authType) throws CertificateException { + } + + @Override + public void checkClientTrusted(final X509Certificate[] chain, + final String authType) throws CertificateException { + } + }}; + + + try { + SSLContext sslContext = SSLContext.getInstance("SSL"); + + sslContext.init(null, trustAllCerts, new SecureRandom()); + clientBuilder.sslSocketFactory(sslContext.getSocketFactory()); + + HostnameVerifier hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + Log.d(TAG, "Trust Host :" + hostname); + return true; + } + }; + clientBuilder.hostnameVerifier(hostnameVerifier); + } catch (Exception e) { + e.printStackTrace(); + } + } + + OkHttpClient httpClient = clientBuilder.build(); + FileDownloader.CallbackToDownloadFile cb = new FileDownloader.CallbackToDownloadFile(info.mSaveDir, info.mSaveName); + + Request request = new Request.Builder() + .url(info.mUrl) + .build(); + + httpClient.newCall(request).enqueue(cb); + + while (mDownloadStatus == DOWNLOAD_STATUS_PROCESS) { + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if (mDownloadStatus == DOWNLOAD_STATUS_FAILED) { + info.mTryCount++; + if (info.mTryCount < 10) { + mDownList.offer(info); + } + } + + MainActivity.INSTANCE.doSync(); + } + + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + +} diff --git a/app/src/main/java/kr/co/rito/osicmanager/JsonManager.java b/app/src/main/java/kr/co/rito/osicmanager/JsonManager.java new file mode 100644 index 0000000..5486eb0 --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/JsonManager.java @@ -0,0 +1,386 @@ +package kr.co.rito.osicmanager; + +import android.content.Context; +import android.util.Log; +import android.widget.Toast; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +class JsonManager extends Thread { + private static String TAG = "JsonManager"; + int DOWNLOAD_STATUS_PROCESS = 0; + int DOWNLOAD_STATUS_SUCCESS = 1; + int DOWNLOAD_STATUS_FAILED = 2; + private int mDownloadStatus = DOWNLOAD_STATUS_PROCESS; + private String mJsonUrlPath; + private Context mContext; + + private class CallbackToDownloadFile implements Callback { + + private File directory; + private File fileToBeDownloaded; + + public CallbackToDownloadFile(String directory, String fileName) { + this.directory = new File(directory); + this.fileToBeDownloaded = new File(this.directory.getAbsolutePath() + "/" + fileName); + } + + @Override + public void onFailure(Call call, IOException e) { + mDownloadStatus = DOWNLOAD_STATUS_FAILED; + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (!this.directory.exists()) { + this.directory.mkdirs(); + } + + if (this.fileToBeDownloaded.exists()) { + this.fileToBeDownloaded.delete(); + } + + try { + this.fileToBeDownloaded.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + + mDownloadStatus = DOWNLOAD_STATUS_FAILED; + return; + } + + InputStream is = response.body().byteStream(); + OutputStream os = new FileOutputStream(this.fileToBeDownloaded); + + final int BUFFER_SIZE = 2048; + byte[] data = new byte[BUFFER_SIZE]; + + int count; + long total = 0; + + while ((count = is.read(data)) != -1) { + total += count; + os.write(data, 0, count); + } + + os.flush(); + os.close(); + is.close(); + + mDownloadStatus = DOWNLOAD_STATUS_SUCCESS; + } + } + + public JsonManager(Context context) { + mContext = context; + } + + public void setDownloadPath(String jsonUrl) { + mJsonUrlPath = jsonUrl; + } + + + @Override + public void run() { + if(mJsonUrlPath == null) + return; + + boolean allowUntrusted = true; + OkHttpClient client = new OkHttpClient(); + OkHttpClient.Builder clientBuilder = client.newBuilder().readTimeout(10, TimeUnit.SECONDS); + if(allowUntrusted) { + final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + X509Certificate[] cArrr = new X509Certificate[0]; + return cArrr; + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, + final String authType) throws CertificateException { + } + + @Override + public void checkClientTrusted(final X509Certificate[] chain, + final String authType) throws CertificateException { + } + }}; + + + try { + SSLContext sslContext = SSLContext.getInstance("SSL"); + + sslContext.init(null, trustAllCerts, new SecureRandom()); + clientBuilder.sslSocketFactory(sslContext.getSocketFactory()); + + HostnameVerifier hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + Log.d(TAG, "Trust Host :" + hostname); + return true; + } + }; + clientBuilder.hostnameVerifier( hostnameVerifier); + } catch(Exception e) { + e.printStackTrace(); + } + } + + Request request; + OkHttpClient httpClient; + CallbackToDownloadFile cb; + int retry_count = 0; + do { + mDownloadStatus = DOWNLOAD_STATUS_PROCESS; + httpClient = clientBuilder.build(); + cb = new CallbackToDownloadFile("/tmp", "schedule.json"); + request = new Request.Builder() + .url(mJsonUrlPath) + .build(); + + httpClient.newCall(request).enqueue(cb); + + while (mDownloadStatus == DOWNLOAD_STATUS_PROCESS) { + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if (mDownloadStatus == DOWNLOAD_STATUS_FAILED) { + retry_count++; + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } while(retry_count < 10 && mDownloadStatus == DOWNLOAD_STATUS_FAILED); + + if (mDownloadStatus == DOWNLOAD_STATUS_FAILED) { + Log.w(TAG, "Failed to download JSON [" + mJsonUrlPath + "]"); + return; + } else if (mDownloadStatus == DOWNLOAD_STATUS_SUCCESS) { + Log.i(TAG, "Success to download JSON [" + mJsonUrlPath + "] at retry count : " + retry_count); + } + + File sourceFile = new File("/tmp/schedule.json"); + String destPath = MainActivity.INSTANCE.mSDSavePath; + try { + File destDirectory = new File(destPath); + if (!destDirectory.exists()) { + destDirectory.mkdir(); + } + } catch(Exception e) { + MainActivity.INSTANCE.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MainActivity.INSTANCE, "MicroSD 동작에 문제가 있습니다.", Toast.LENGTH_LONG).show(); + } + }); + + mDownloadStatus = DOWNLOAD_STATUS_FAILED; + return; + } + + SystemUtil.TraceLog("JsonManager Trace Before [File destFile = new File]"); + File destFile = new File(destPath + "/schedule.json"); + SystemUtil.copyFile(sourceFile, destFile); + MainActivity.INSTANCE.doSync(); + + SystemUtil.TraceLog("JsonManager Trace Before [File checkFile = new File]"); + File checkFile = new File(destPath + "/schedule.json.check"); + if(checkFile.exists()) { + checkFile.delete(); + MainActivity.INSTANCE.doSync(); + } + + JsonResult jsonResult = loadJsonData(); + if(jsonResult == null) { + Log.w(TAG, "Failed to download JSON!!!!. jsonResult is null"); + return; + } + + if(jsonResult.mType.equalsIgnoreCase(MainActivity.MANAGER_JSON_ORDER_TYPE_PLAY_CONTENTS)) { // Download Contents Files + for(int i = 0; i < jsonResult.mFileCount;i++) { + mDownloadStatus = DOWNLOAD_STATUS_PROCESS; + + String cksum = MainActivity.INSTANCE.getFileCksum(MainActivity.INSTANCE.mSDSavePath + "/" + jsonResult.mFilePathList[i]); + if(cksum.equalsIgnoreCase(jsonResult.mFileCksumList[i])) { + Log.d(TAG, "Already downloaded file : " + jsonResult.mFilePathList[i]); + continue; + } + + for(int retry = 0;retry < 3;retry++) { + cb = new CallbackToDownloadFile(MainActivity.INSTANCE.mSDSavePath, jsonResult.mFilePathList[i]); + + request = new Request.Builder() + .url(jsonResult.mFileUrlList[i]) + .build(); + + Log.d(TAG, "Start Download File : " + jsonResult.mFileUrlList[i]); + + httpClient.newCall(request).enqueue(cb); + + while (mDownloadStatus == DOWNLOAD_STATUS_PROCESS) { + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + MainActivity.INSTANCE.doSync(); + + cksum = MainActivity.INSTANCE.getFileCksum(MainActivity.INSTANCE.mSDSavePath + "/" + jsonResult.mFilePathList[i]); + if (cksum.equalsIgnoreCase(jsonResult.mFileCksumList[i])) { + Log.d(TAG, "다운로드 파일 정상 [" + jsonResult.mFilePathList[i] + "]"); + break; + } else { + Log.d(TAG, "다운로드 파일 체크섬 불일치 [" + jsonResult.mFilePathList[i] + "]. 재시도 횟수 : " + (retry + 1)); + } + } + } + + Log.d(TAG, "Contents Download Complete..."); + + checkFile = new File(destPath + "/schedule.json.check"); + if(!checkFile.exists()) { + try { + checkFile.createNewFile(); + MainActivity.INSTANCE.doSync(); + } catch(Exception e) { + } + } + + File backupFile = new File(destPath + "/file-schedule.json"); + SystemUtil.copyFile(sourceFile, backupFile); + MainActivity.INSTANCE.doSync(); + + Log.d(TAG, "Do updateJsonTargetResult as File-Schedule..."); + MainActivity.INSTANCE.mJsonScheduler.updateJsonTargetResult("file", jsonResult); + } + + MainActivity.INSTANCE.mJsonScheduler.updateJsonResult(jsonResult); + MainActivity.INSTANCE.mJsonScheduler.prepareJsonSchedule(); + } + + public JsonResult loadJsonData() { + return loadJsonData("schedule.json"); + } + + public static JsonResult loadJsonData(String jsonFileName) { + String destPath = MainActivity.INSTANCE.mSDSavePath; + File destFile = new File(destPath + "/" + jsonFileName); + try { + FileInputStream stream = new FileInputStream(destFile); + String jString = null; + try { + FileChannel fc = stream.getChannel(); + MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); + jString = Charset.defaultCharset().decode(bb).toString(); + } finally { + stream.close(); + } + + return parseJson(jString); + } catch(Exception e) { + e.printStackTrace(); + } + + return null; + } + + static JsonResult parseJson(String jsonData) { + JsonResult res = new JsonResult(); + try { + JSONObject topObject = new JSONObject(jsonData); + String jsonType = topObject.getString("type"); + + if(jsonType.equalsIgnoreCase(MainActivity.MANAGER_JSON_ORDER_TYPE_PLAY_IPCAST)) { + res.mType = MainActivity.MANAGER_JSON_ORDER_TYPE_PLAY_IPCAST; + JSONArray jsonArray = topObject.getJSONArray("data"); + res.mIpCount = jsonArray.length(); + res.mIpAddressList = new String[res.mIpCount]; + res.mIpTitleList = new String[res.mIpCount]; + res.mIpNumberList = new String[res.mIpCount]; + res.mIpSelectedIndex = 0; + for (int i = 0; i < res.mIpCount; i++) { + JSONObject jo = jsonArray.getJSONObject(i); + + res.mIpAddressList[i] = jo.getString("address"); + if(res.mIpAddressList[i].startsWith("live://")) { + res.mIpAddressList[i] = res.mIpAddressList[i].replace("live://", "udp://"); + if(!res.mIpAddressList[i].contains("@")) { + res.mIpAddressList[i] = res.mIpAddressList[i].replace("udp://", "udp://@"); + } + } + res.mIpTitleList[i] = jo.getString("title"); + res.mIpNumberList[i] = jo.getString("ch_no"); + if(jo.getBoolean("selected")) + res.mIpSelectedIndex = i; + } + } else if(jsonType.equalsIgnoreCase(MainActivity.MANAGER_JSON_ORDER_TYPE_PLAY_CARCAST)) { + res.mType = MainActivity.MANAGER_JSON_ORDER_TYPE_PLAY_CARCAST; + JSONArray carList = topObject.getJSONArray("data"); + JSONObject carData = carList.getJSONObject(0); + res.mCarAddress = carData.getString("url"); + res.mCarCar = carData.getString("car"); + res.mCarStation = carData.getString("station"); + } else if(jsonType.equalsIgnoreCase(MainActivity.MANAGER_JSON_ORDER_TYPE_PLAY_CONTENTS)) { + res.mType = MainActivity.MANAGER_JSON_ORDER_TYPE_PLAY_CONTENTS; + JSONArray jsonArray = topObject.getJSONArray("data"); + res.mFileCount = jsonArray.length(); + res.mFileUrlList = new String[res.mFileCount]; + res.mFilePathList = new String[res.mFileCount]; + res.mFileTitleList = new String[res.mFileCount]; + res.mFileDurationList = new int[res.mFileCount]; + res.mFileCksumList = new String[res.mFileCount]; + for (int i = 0; i < res.mFileCount; i++) { + JSONObject jo = jsonArray.getJSONObject(i); + + res.mFileUrlList[i] = jo.getString("url"); + res.mFilePathList[i] = res.mFileUrlList[i].substring(res.mFileUrlList[i].lastIndexOf('/') + 1); + res.mFileTitleList[i] = jo.getString("name"); + res.mFileDurationList[i] = jo.getInt("duration"); + res.mFileCksumList[i] = jo.getString("cksum"); + } + } + } catch(Exception e) { + e.printStackTrace(); + return null; + } + + return res; + } + +} diff --git a/app/src/main/java/kr/co/rito/osicmanager/JsonResult.java b/app/src/main/java/kr/co/rito/osicmanager/JsonResult.java new file mode 100644 index 0000000..14d405c --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/JsonResult.java @@ -0,0 +1,25 @@ +package kr.co.rito.osicmanager; + +public class JsonResult { + public String mType; + + // For IpCast + public int mIpCount; + public int mIpSelectedIndex; + public String[] mIpAddressList; + public String[] mIpTitleList; + public String[] mIpNumberList; + + // For CarCast + public String mCarAddress; + public String mCarStation; + public String mCarCar; + + // For FileCast + public int mFileCount; + public String[] mFileUrlList; + public String[] mFileTitleList; + public int[] mFileDurationList; + public String[] mFilePathList; + public String[] mFileCksumList; +} diff --git a/app/src/main/java/kr/co/rito/osicmanager/MainActivity.java b/app/src/main/java/kr/co/rito/osicmanager/MainActivity.java new file mode 100644 index 0000000..47f9591 --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/MainActivity.java @@ -0,0 +1,4714 @@ +package kr.co.rito.osicmanager; + +import androidx.annotation.OptIn; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatTextView; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.text.LineBreaker; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; +import android.media.MediaPlayer; +import android.media.SoundPool; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.FileUtils; +import android.os.SystemClock; +import android.provider.MediaStore; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.JsonReader; +import android.util.Log; +import android.view.Display; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.TextureView; +import android.view.View; +import android.view.WindowManager; +import android.webkit.JavascriptInterface; +import android.webkit.WebResourceError; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.PlaybackException; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; +import com.google.android.exoplayer2.source.hls.HlsManifest; +import com.google.android.exoplayer2.upstream.AssetDataSource; +import com.google.android.exoplayer2.upstream.DataSource; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +//import com.downloader.Error; +//import com.downloader.OnCancelListener; +//import com.downloader.OnDownloadListener; +//import com.downloader.OnPauseListener; +//import com.downloader.OnProgressListener; +//import com.downloader.OnStartOrResumeListener; +//import com.downloader.PRDownloader; +//import com.downloader.PRDownloaderConfig; +//import com.downloader.Progress; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import kr.co.rito.ritoplayer.RitoPlayer; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import org.eclipse.paho.android.service.MqttAndroidClient; +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.IMqttMessageListener; +import org.eclipse.paho.client.mqttv3.IMqttToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.json.JSONArray; +import org.json.JSONObject; +import org.videolan.libvlc.interfaces.IMedia; +import org.videolan.libvlc.interfaces.IVLCVout; +import org.videolan.libvlc.LibVLC; +import org.videolan.libvlc.Media; +//import org.videolan.libvlc.MediaPlayer; + +public class MainActivity extends AppCompatActivity implements IVLCVout.Callback, TextureView.SurfaceTextureListener { + public final static String TAG = "OsicMain"; + public static int PLAYER_LIBRARY_TYPE_RITO = 1; + public static int PLAYER_LIBRARY_TYPE_VLC = 2; + public static int USING_MAIN_PLAYER_LIBRARY_TYPE = PLAYER_LIBRARY_TYPE_VLC; + //public static int USING_MAIN_PLAYER_LIBRARY_TYPE = PLAYER_LIBRARY_TYPE_RITO; + + public static int PLAYER_VIEW_TYPE_SURFACE_VIEW = 1; + public static int PLAYER_VIEW_TYPE_TEXTURE_VIEW = 2; + public static int USING_PLAYER_VIEW_TYPE = PLAYER_VIEW_TYPE_TEXTURE_VIEW; + + static int MANAGER_NOTIFY_ORDER_START_PLAYBACK = 1; + static int MANAGER_NOTIFY_ORDER_STOP_PLAYBACK = 2; + static int MANAGER_NOTIFY_ORDER_START_TICKER = 3; + static int MANAGER_NOTIFY_ORDER_START_TTS = 4; + static int MANAGER_NOTIFY_ORDER_CEC_CONTROL = 5; + static int MANAGER_NOTIFY_ORDER_JSON_ORDER = 6; + static int MANAGER_NOTIFY_ORDER_NOTIFY_SCREEN_ORDER = 7; + static int MANAGER_NOTIFY_ORDER_EVENT_MESSAGE = 8; + + static String MANAGER_JSON_ORDER_TYPE_PLAY_CONTENTS = "filecast"; + static String MANAGER_JSON_ORDER_TYPE_PLAY_IPCAST = "ipcast"; + static String MANAGER_JSON_ORDER_TYPE_PLAY_CARCAST = "carcast"; + + static String MANAGER_ACTION_MODE_STANDBY = "standby"; + static String MANAGER_ACTION_MODE_SERVICE = "inservice"; + + static int VLC_NETWORK_CACHE_DEFAULT = 1500; + static int VLC_LIVE_CACHE_DEFAULT = 1500; + static int VLC_UDP_BUFFER_DEFAULT = 32768; + + private long mKeyPressTime; + private long mNotifyMsgStartTime; + public boolean mIsOnNotifyMsg; + public boolean mIsOnTTSPlaying; + public boolean mIsDeviceOSIC10; + private int mLastSpeakerVolume; + private int mLastDacVolume; + private int mLastVideoVolume; + + private SurfaceView mSurface; + private TextureView mTexturePlayView; + private SurfaceHolder holder; + private LibVLC mVLC; + private String mCurrentPlayUrl; + private String mLastPlayUrl; + private String mShouldPlayUrl; + private boolean mShouldStopPlay = false; + org.videolan.libvlc.MediaPlayer mVlcPlayer; + private Media mVLCMedia; + private int mVlcVolume; + private int mVlcMaxVolume; + + ImageView mChannelOsdImageView; + ImageView mImageViewBlack; + TextView mChannelOsdView; + ProgressBar mProgressBar; + + TextView mCarCastTitleView; + + private String mManagerActMode; + public String mWardId; // for 119 + public String mDsrSeq; // for 119 + public String mCastReceivedNotifyDsrSeq; // for 119 + public long mDsrCastReceivedTime; // for 119 + public String mEbcastMqttIp; // for 119 + public static final int EBCAST_SAMPLE_RATE = 16000; // 샘플링 레이트 (Hz) + public static final int EBCAST_CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_MONO; // 모노 + public static final int EBCAST_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; // 16비트 PCM + public AudioTrack mAudioTrack; + public ExoPlayer mExoPlayer = null; + + + boolean mIsRitoPlayerOver; + RitoPlayer mPlayer; + RitoPlayer mAudioPlayer; + RitoPlayer.playerNotify mPlayerNotify; + RitoPlayer.AudioOut mAudioSubOut; + AudioFilePlayer mTTSPlayer; + TickerManager mTickerManager; + VfdManager mVfdManager; + SystemManager mSystemManager; + CoolerManager mCoolerManager; + FileDownloader mFileDownloader; + public static MainActivity INSTANCE; + + public String mSDCardPath; + public String mSDSavePath; + public String mEmmcSavePath; + public JsonResult mJsonResult; + public JsonResult mJsonIpSchedule; + public JsonResult mJsonFileSchedule; + public JsonScheduler mJsonScheduler; + public DacScheduler mDacScheduler; + + private static SoundPool mSoundPool; + private static HashMap mSoundPoolMap; + + public static MqttAndroidClient mqttAndroidClient = null; + + // Used to load the 'osic-lib' library on application startup. + static { + System.loadLibrary("osic-lib"); + } + + private static void shellCommand(String order) { + try { + Process process = Runtime.getRuntime().exec(order); + process.waitFor(); + process.destroy(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void shellCommand(String[] order) { + try { + Process process = Runtime.getRuntime().exec(order); + process.waitFor(); + process.destroy(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onSurfacesCreated(IVLCVout ivlcVout) { + + } + + @Override + public void onSurfacesDestroyed(IVLCVout ivlcVout) { + + } + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) { + Log.w(TAG, "Surfacee Texture Created!!"); + //createPlayer("http://219.255.217.140/ext/contents/Your_wedding.ts"); + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + createPlayer(null); + } + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) { +// final IVLCVout vout = mVlcPlayer.getVLCVout(); +// vout.detachViews(); +// vout.setVideoView(mTexturePlayView); +// vout.attachViews(); +// mVlcPlayer.updateVideoSurfaces(); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + return false; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + + } + + + + class CoolerManager extends Thread { + public int CPU_FAN_GPIO_NUMBER = 156; + public boolean mIsAlive = false; + + public void terminate() { + mIsAlive = false; + } + + public void initialize() { + SystemUtil.pinSetup(CPU_FAN_GPIO_NUMBER, SystemUtil.GPIO_OUT); + } + + public void activateCpuFan(boolean activate) { + if(activate) { + SystemUtil.setPinValue(CPU_FAN_GPIO_NUMBER, SystemUtil.GPIO_HIGH); + } else { + SystemUtil.setPinValue(CPU_FAN_GPIO_NUMBER, SystemUtil.GPIO_LOW); + } + } + + @Override + public void run() { + mIsAlive = true; + while(mIsAlive) { + activateCpuFan(true); + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + activateCpuFan(false); + try { + sleep(300); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + public void setSpeakerVolume(int volume) { + try { + int volumeInt = volume; + if(mLastSpeakerVolume != volumeInt) { + mLastSpeakerVolume = volumeInt; + + String order = String.format("ritoi2c w /dev/i2c-6 0x31 0x13 0x%X", (100 - mLastSpeakerVolume)); + shellCommand(order); + } + } catch(Exception e) { + } + } + + public void setVideoVolume(int volume) { + try { + int volumeInt = volume; + if(mLastVideoVolume != volumeInt) { + mLastVideoVolume = volumeInt; + + String order = String.format("ritosysc SHELL-ORDER=media volume --show --stream 3 --set %d", mLastVideoVolume); + shellCommand(order); + } + } catch(Exception e) { + } + } + + public void setDacVolume(int volume) { + try { + int volumeInt = volume; + if(mLastDacVolume != volumeInt) { + mLastDacVolume = volumeInt; + + } + } catch(Exception e) { + } + } + + class SystemManager extends Thread { + public int CPU_FAN_GPIO_NUMBER = 156; + public boolean mIsAlive = false; + + public void terminate() { + mIsAlive = false; + } + + public void initialize() { + SystemUtil.pinSetup(CPU_FAN_GPIO_NUMBER, SystemUtil.GPIO_OUT); + } + + public void activateCpuFan(boolean activate) { + if(activate) { + SystemUtil.setPinValue(CPU_FAN_GPIO_NUMBER, SystemUtil.GPIO_HIGH); + } else { + SystemUtil.setPinValue(CPU_FAN_GPIO_NUMBER, SystemUtil.GPIO_LOW); + } + } + + public void stopNotifyScreen() { + runOnUiThread(new Runnable() { + @Override + public void run() { + findViewById(R.id.surfaceViewCmd).setVisibility(View.INVISIBLE); + findViewById(R.id.layoutCommand).setVisibility(View.INVISIBLE); +// findViewById(R.id.surfaceViewCmd).setAlpha(0.0f); +// findViewById(R.id.layoutCommand).setAlpha(0.0f); + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + if(mVlcPlayer != null) { + setMainPlayerVolume(mVlcMaxVolume); + } + } + } + }); + + Log.w(TAG, "**********************"); + Log.w(TAG, " 지령화면 표시종료 "); + Log.w(TAG, "**********************"); + + shellCommand("miscctrl function_led red off"); + shellCommand("miscctrl function_led green off"); + shellCommand("miscctrl function_led blue off"); + } + + @Override + public void run() { + int aliveCount = 0; + int mSpkVolumeCheckCount = 20; + mIsAlive = true; + while(mIsAlive) { + long curTime = SystemClock.uptimeMillis(); + if(BuildConfig.SPECIAL_APP_MODE.equals("119NOTI_DEMO")) { + curTime = mNotifyMsgStartTime; + } + + if(mNotifyMsgStartTime > 0) { + if(curTime - mNotifyMsgStartTime > 60 * 1000) { // 30 * 1000 -> 60 * 1000 by ritoseo - 2021.04.08 + mNotifyMsgStartTime = 0; + mIsOnNotifyMsg = false; + stopNotifyScreen(); + } + } + + mSpkVolumeCheckCount++; + if(mSpkVolumeCheckCount >= 10) { + mSpkVolumeCheckCount = 0; + String volume = SystemUtil.getConfigValue("spkvolume"); + if(volume.length() == 0) { + volume = "100"; + } + + try { + int volumeInt = Integer.parseInt(volume); + setSpeakerVolume(volumeInt); + } catch(Exception e) { + } + + volume = SystemUtil.getConfigValue("videovolume"); + if(volume.length() == 0) { + volume = "12"; + } + + try { + int volumeInt = Integer.parseInt(volume); + setVideoVolume(volumeInt); + } catch(Exception e) { + } + + + volume = SystemUtil.getConfigValue("dacvolume"); + if(volume.length() == 0) { + volume = "100"; + } + + //Log.d("RITO", "dacvolume : " + volume); + try { + int volumeInt = Integer.parseInt(volume); + setDacVolume(volumeInt); + } catch(Exception e) { + } + } + + if(!SystemUtil.property_get("sys.rito.freeze").equals("liveness")) { + aliveCount++; + aliveCount %= 10000000; + SystemUtil.property_set("sys.rito.osic.liveness", "" + aliveCount); + } + + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + class VfdManager extends Thread { + public int VFD_TOTAL_WIDTH = 16; + public int VFD_TOTAL_HEIGHT = 2; + public int VFD_ALIGN_LEFT = 1; + public int VFD_ALIGN_CENTER = 2; + public int VFD_ALIGN_RIGHT = 3; + private long mChangedTime = 0; + + private Object mLock = new Object(); + private int mSetIdx = -1; + private int mChangeSection = 0; + public String[] mSettingOrder = { "STATION IP", "STATION NM", "STATION GW", "STATION DNS", "SERVER IP", "SERVER PORT" }; + + public boolean mVfdChangeMode = false; + public boolean mVfdConfirmMode = false; // added by ritoseo - 2023-03-21 + public boolean mVfdConfirmYesNo = false; // added by ritoseo - 2023-03-21 + private int mVfdChangeType = 0; + private String mVfdChangingValue = ""; + + private String mVfdBaseString = ""; + + private boolean isTitleIpType(String title) { + if(title.equals("STATION IP") || + title.equals("STATION NM") || + title.equals("STATION GW") || + title.equals("STATION DNS") || + title.equals("SERVER IP")) { + return true; + } + + return false; + } + + private String currentTitleString() { + int idx; + synchronized (mLock) { + idx = mSetIdx; + } + if(idx < 0) + return "OSIC-10"; + + return mSettingOrder[idx]; + } + + public void cancelVfdChangeMode() { + if(mSetIdx < 0) + return; + + if(mVfdChangeMode) { + moveCursor(0, 0, 0); + mChangedTime = SystemClock.uptimeMillis(); + mVfdChangeMode = false; + } + } + + public void toggleVfdChangeMode() { + //Log.w(TAG, "VfdChangeMode : " + mVfdChangeMode); + if(mSetIdx < 0) + return; + + if(mVfdChangeMode) { + moveCursor(0, 0, 0); + /* Added by ritoseo - 2023-03-21 */ +// if(!mVfdConfirmMode && confirmSettingCheck()) { +// mVfdConfirmMode = true; +// mVfdConfirmYesNo = true; +// vfdDrawString("SAVE CHANGES?", 0, VFD_ALIGN_CENTER); +// toggleConfirmValue(); +// return; +// } + /* ------------------------------ */ + confirmSettingValue(); + mVfdChangeMode = false; + } else { + mVfdChangeMode = true; + String curTitle = currentTitleString(); + + if(isTitleIpType(curTitle)) { + if(mVfdChangingValue.length() == 0) + mVfdChangingValue = "0.0.0.0"; + + vfdDrawString(mVfdChangingValue, 1, VFD_ALIGN_CENTER); + String padString = makePaddingString(mVfdChangingValue); + int pos = padString.indexOf('.'); + if(pos > 0) { + mChangeSection = 0; + moveCursor(pos - 1, 1, 1); + } + } + } + } + + private String changeSectionValue(String valueString, int section, int direction) { + String result; + String[] splitValue = valueString.split("\\."); + + Log.w(TAG, "splitValue Length : " + splitValue.length + ", valueString : " + valueString); + if(splitValue.length < 4) + return valueString; + + int value = Integer.parseInt(splitValue[section]); + value += direction; + if(value < 0) + value = 255; + value %= 256; + + splitValue[section] = "" + value; + + result = String.format("%s.%s.%s.%s", splitValue[0], splitValue[1], splitValue[2], splitValue[3]); + return result; + } + + private int getCursorOfSection(String valueString, int section) { + if(section == 3) { + for(int i = valueString.length() - 1;i >= 0;i--) { + if(valueString.charAt(i) >= '0' && valueString.charAt(i) <= '9') + return i; + } + + return valueString.length() - 1; + } + + int cnt = -1; + for(int i = 0;i < valueString.length();i++) { + if(valueString.charAt(i) == '.') { + cnt++; + if(cnt == section) { + return i - 1; + } + } + } + + return 0; + } + + public void applyNetworkSettings() { + String ipaddr = getConfigValue("ipaddr"); + String netmask = getConfigValue("netmask"); + String gateway = getConfigValue("gateway"); + String dns = getConfigValue("dns"); + + Log.w(TAG, String.format("[applyNetwork] %s, %s, %s, %s", ipaddr, netmask, gateway, dns)); + if(ipaddr.length() > 0 && netmask.length() > 0 && gateway.length() > 0 && dns.length() > 0) { + shellCommand("setConfig iptype static"); + } + } + + public boolean confirmSettingCheck() { + String title = currentTitleString(); + boolean needConfirm = false; + + if(title.equals("STATION IP")) { + needConfirm = true; + } else if(title.equals("STATION NM")) { + needConfirm = true; + } else if(title.equals("STATION GW")) { + needConfirm = true; + } else if(title.equals("STATION DNS")) { + needConfirm = true; + } else if(title.equals("SERVER IP")) { + needConfirm = true; + } + + return needConfirm; + } + + private void drawSaveVfd() { + vfdDrawString("SAVE", 0, VFD_ALIGN_CENTER); + vfdDrawString("", 1, VFD_ALIGN_CENTER); + } + + public void confirmSettingValue() { + String title = currentTitleString(); + + if(title.equals("STATION IP")) { + drawSaveVfd(); + shellCommand("setConfig ipaddr " + mVfdChangingValue); + applyNetworkSettings(); + } else if(title.equals("STATION NM")) { + drawSaveVfd(); + shellCommand("setConfig netmask " + mVfdChangingValue); + applyNetworkSettings(); + } else if(title.equals("STATION GW")) { + drawSaveVfd(); + shellCommand("setConfig gateway " + mVfdChangingValue); + applyNetworkSettings(); + } else if(title.equals("STATION DNS")) { + drawSaveVfd(); + shellCommand("setConfig dns " + mVfdChangingValue); + applyNetworkSettings(); + } else if(title.equals("SERVER IP")) { + drawSaveVfd(); + shellCommand("setConfig svraddr " + mVfdChangingValue); + } + } + + private void toggleConfirmValue() { + mVfdConfirmYesNo = !mVfdConfirmYesNo; + if(mVfdConfirmMode) { + vfdDrawString("YES", 1, VFD_ALIGN_CENTER); + } else { + vfdDrawString("NO", 1, VFD_ALIGN_CENTER); + } + } + + public void changeSettingValue(int direction) { +// if(mVfdConfirmMode) { +// toggleConfirmValue(); +// return; +// } + + if(mVfdChangeMode) { + mVfdChangingValue = changeSectionValue(mVfdChangingValue, mChangeSection, direction); + vfdDrawString(mVfdChangingValue, 1, VFD_ALIGN_CENTER); + + String padString = makePaddingString(mVfdChangingValue); + int pos = getCursorOfSection(padString, mChangeSection); + moveCursor(pos, 1, 1); + } + } + + public void moveSettingMode(int direction) { + int idx; +// if(mVfdConfirmMode) { +// return; +// } + + if(mVfdChangeMode) { + String curTitle = currentTitleString(); + + if(isTitleIpType(curTitle)) { + //String[] valueDigit = mVfdChangingValue.split("."); + mChangeSection += direction; + if(mChangeSection < 0) + mChangeSection = 3; + mChangeSection %= 4; + + String padString = makePaddingString(mVfdChangingValue); + int pos = getCursorOfSection(padString, mChangeSection); + moveCursor(pos, 1, 1); + } + + return; + } + + synchronized (mLock) { + mSetIdx += direction; + if (mSetIdx < 0) + mSetIdx = mSettingOrder.length - 1; + mSetIdx %= mSettingOrder.length; + idx = mSetIdx; + } + printModeTitle(idx); + printModeValue(idx); + mChangedTime = SystemClock.uptimeMillis(); + } + + private void setSettingMode(int index) { + synchronized (mLock) { + mSetIdx = index; + } + + printModeTitle(index); + printModeValue(index); + mChangedTime = SystemClock.uptimeMillis(); + } + + private void printModeTitle(int mode) { + if(mode < 0) { // clear + vfdDrawString("OSIC-10", 0, VFD_ALIGN_CENTER); + return; + } + vfdDrawString(mSettingOrder[mode], 0, VFD_ALIGN_CENTER); + } + + private void moveCursor(int x, int y, int mode) { + String[] order = {"vfdctrl", "cursorxy", "" + x, "" + y}; + shellCommand(order); + if(mode == 0) { + shellCommand("vfdctrl showcursor off"); + } else { + shellCommand("vfdctrl showcursor on"); + } + } + + private boolean isIptypeStatic() { + String iptype = getConfigValue("ipv4type"); + if(iptype.equals("static")) + return true; + + return false; + } + + private void printModeValue(int mode) { + String value = ""; + if(mode < 0) { // clear + vfdDrawString("", 1, VFD_ALIGN_CENTER); + return; + } + if(mSettingOrder[mode].equals("STATION IP")) { + value = getIPAddress(); + if(isIptypeStatic()) { + String temp = getConfigValue("ipaddr"); + if(temp.length() > 0) + value = temp; + } + } else if(mSettingOrder[mode].equals("STATION NM")) { + value = getNetmask(); + if(isIptypeStatic()) { + String temp = getConfigValue("netmask"); + if(temp.length() > 0) + value = temp; + } + } else if(mSettingOrder[mode].equals("STATION GW")) { + value = getGateway(); + if(isIptypeStatic()) { + String temp = getConfigValue("gateway"); + if(temp.length() > 0) + value = temp; + } + } else if(mSettingOrder[mode].equals("STATION DNS")) { + value = SystemUtil.property_get("net.dns1"); + if(isIptypeStatic()) { + String temp = getConfigValue("dns"); + if(temp.length() > 0) + value = temp; + } + } else if(mSettingOrder[mode].equals("SERVER IP")) { + value = getServerAddress(); + } else if(mSettingOrder[mode].equals("SERVER PORT")) { + value = "" + getServerPort(); + } + + Log.w(TAG, "VALUE : " + value); + mVfdChangingValue = value; + vfdDrawString(value, 1, VFD_ALIGN_CENTER); + } + + String makePaddingString(String string) { + int x; + int length; + String strMessage = ""; + String strLeftPadding = ""; + String strRightPadding = ""; + length = string.length(); + x = (VFD_TOTAL_WIDTH - length) / 2; + if(x > 0) { + for(int i = 0; i < x;i++) + strLeftPadding += " "; + } + if(VFD_TOTAL_WIDTH - length - x > 0) { + for(int i = 0; i < VFD_TOTAL_WIDTH - length - x;i++) + strRightPadding += " "; + } + strMessage = strLeftPadding + string + strRightPadding; + + return strMessage; + } + + void vfdDrawString(String string, int y, int flag) + { + int x; + int length; + String strMessage = ""; + String strLeftPadding = ""; + String strRightPadding = ""; + + length = string.length(); + + if(flag == VFD_ALIGN_LEFT) { + x = 0; + + if(VFD_TOTAL_WIDTH - length > 0) { + for(int i = 0; i < VFD_TOTAL_WIDTH - length;i++) + strRightPadding += ' '; + } + } else if(flag == VFD_ALIGN_CENTER) { + x = (VFD_TOTAL_WIDTH - length) / 2; + if(x > 0) { + for(int i = 0; i < x;i++) + strLeftPadding += " "; + } + if(VFD_TOTAL_WIDTH - length - x > 0) { + for(int i = 0; i < VFD_TOTAL_WIDTH - length - x;i++) + strRightPadding += " "; + } + } else { + x = VFD_TOTAL_WIDTH - length; + } + + strMessage = strLeftPadding + string + strRightPadding; + printVfdMessage(0, y, strMessage); + } + + public void printVfdMessage(int x, int y, String msg) { + /*Log.w(TAG, String.format("%d, %d, MSG : %s", x, y, msg));*/ + /*String order = String.format("vfdctrl printxy %d %d %s", x, y, msg); + try { + String newOrder = new String(order.getBytes("US-ASCII"), "US-ASCII"); + Log.w(TAG, "new order : " + newOrder); + shellCommand(newOrder.trim()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + }*/ + + String[] order = {"vfdctrl", "printxy", "" + x, "" + y, msg}; + shellCommand(order); + } + + + @Override + public void run() { + while(true) { + long curTime = SystemClock.uptimeMillis(); + + if(curTime - mChangedTime >= 7 * 1000) { + if(!mVfdChangeMode) { + setSettingMode(-1); + } + } + + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + class AudioFilePlayer extends Thread { + int DOWNLOAD_STATUS_PROCESS = 0; + int DOWNLOAD_STATUS_SUCCESS = 1; + int DOWNLOAD_STATUS_FAILED = 2; + private boolean mChimeUpDone = false; + private boolean mChimeDownDone = false; + private int mProgress = 0; + private int mLastProgress = 0; + private int mPlayVolume = 100; + private int mDownloadStatus = DOWNLOAD_STATUS_PROCESS; + private String mTTSPath; + private boolean mIsAlive = false; + private String mPlayMode; + private String mPlayTarget; + //MediaPlayer mMediaPlayer; // deprecated by ritoseo - 2024-12-16 + ExoPlayer mExoPlayer; + + private class CallbackToDownloadFile implements Callback { + + private File directory; + private File fileToBeDownloaded; + + public CallbackToDownloadFile(String directory, String fileName) { + this.directory = new File(directory); + this.fileToBeDownloaded = new File(this.directory.getAbsolutePath() + "/" + fileName); + } + + + @Override + public void onFailure(Call call, IOException e) { + mDownloadStatus = DOWNLOAD_STATUS_FAILED; + /*runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText( + MainActivity.this, + "파일을 다운로드할 수 없습니다. 인터넷 연결을 확인하세요.", + Toast.LENGTH_SHORT + ).show(); + } + });*/ + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if(!response.isSuccessful()) { + mDownloadStatus = DOWNLOAD_STATUS_FAILED; + return; + } + + if (!this.directory.exists()) { + this.directory.mkdirs(); + } + + if (this.fileToBeDownloaded.exists()) { + this.fileToBeDownloaded.delete(); + } + + try { + this.fileToBeDownloaded.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + /*runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText( + MainActivity.this, + "다운로드 파일을 생성할 수 없습니다.", + Toast.LENGTH_SHORT + ).show(); + } + });*/ + + mDownloadStatus = DOWNLOAD_STATUS_FAILED; + return; + } + + InputStream is = response.body().byteStream(); + OutputStream os = new FileOutputStream(this.fileToBeDownloaded); + + final int BUFFER_SIZE = 2048; + byte[] data = new byte[BUFFER_SIZE]; + + int count; + long total = 0; + + while ((count = is.read(data)) != -1) { + total += count; + os.write(data, 0, count); + } + + os.flush(); + os.close(); + is.close(); + + mDownloadStatus = DOWNLOAD_STATUS_SUCCESS; + + /*runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText( + MainActivity.this, + "다운로드가 완료되었습니다.", + Toast.LENGTH_SHORT + ).show(); + } + });*/ + } + } + + public AudioFilePlayer() { + mPlayMode = "tts"; +// mMediaPlayer = new MediaPlayer(); +// AudioAttributes aa = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ALARM) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .build(); +// mMediaPlayer.setAudioAttributes(aa); +// mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { +// @Override +// public void onCompletion(MediaPlayer mediaPlayer) { +// if(!mChimeUpDone) { +// mChimeUpDone = true; +// return; +// } +// if(mProgress < mLastProgress) { +// mProgress++; +// return; +// } +// if(!mChimeDownDone) { +// mChimeDownDone = true; +// return; +// } +// } +// }); +// +// mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { +// @Override +// public boolean onError(MediaPlayer mediaPlayer, int i, int i1) { +// return false; +// } +// }); + + + + mExoPlayer = new ExoPlayer.Builder(MainActivity.INSTANCE).build(); + mExoPlayer.addListener(new Player.Listener() { + @Override + public void onTimelineChanged( + Timeline timeline, @Player.TimelineChangeReason int reason) { + Object manifest = mExoPlayer.getCurrentManifest(); + if (manifest != null) { + HlsManifest hlsManifest = (HlsManifest) manifest; + // Do something with the manifest. + } + } + + @Override + public void onPlayerError(PlaybackException error) { + // 오류 처리 + switch (error.errorCode) { + case PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED: + // 네트워크 연결 오류 처리 + Log.e("ExoPlayer", "Network connection error: " + error.getMessage()); + break; + + case PlaybackException.ERROR_CODE_DECODING_FAILED: + // 디코딩 오류 처리 + Log.e("ExoPlayer", "Decoding error: " + error.getMessage()); + break; + + default: + // 그 외의 오류 처리 + Log.e("ExoPlayer", "Playback error: " + error.getMessage()); + break; + } + } + + @Override + public void onPlaybackStateChanged(int playbackState) { + if (playbackState == Player.STATE_ENDED) { + // 재생 완료 이벤트 처리 + if(!mChimeUpDone) { + mChimeUpDone = true; + return; + } + if(mProgress < mLastProgress) { + mProgress++; + return; + } + if(!mChimeDownDone) { + mChimeDownDone = true; + } + } + } + }); + } + + public void setDownloadPath(String ttsPath) { + mTTSPath = ttsPath; + } + public void setPlayCount(int count) { + mLastProgress = count; + } + public void setPlayVolume(int volume) { + mPlayVolume = volume; + } + public void terminate() { + mIsAlive = false; + } + + public void resetPlayer() { + mChimeUpDone = false; + mChimeDownDone = false; + mProgress = 0; + mLastProgress = 0; + mTTSPath = null; + } + + public void setPlayMode(String mode) { + mPlayMode = mode; + } + + public void setPlayTarget(String target) { + mPlayTarget = target; + } + + public void playAssetAudio(String assetName) { +// try { +// if(!assetName.endsWith(".wav")) +// assetName += ".wav"; +// +// AssetFileDescriptor afd = getAssets().openFd(assetName); +// mMediaPlayer.reset(); +// mMediaPlayer.setDataSource(afd); +// afd.close(); +// mMediaPlayer.prepare(); +// mMediaPlayer.start(); +// } catch (IOException e) { +// e.printStackTrace(); +// } + + if(!assetName.endsWith(".wav")) + assetName += ".wav"; + + playExoWithAsset(assetName); + } + + public void playExoWithAsset(final String urlName) { + runOnUiThread(new Runnable() { + @Override + public void run() { + MediaSource mediaSource = buildMediaSource(urlName); + mExoPlayer.setMediaSource(mediaSource); + mExoPlayer.prepare(); + mExoPlayer.play(); + } + }); + } + + public void playExoWithUrl(final String urlName) { + runOnUiThread(new Runnable() { + @Override + public void run() { + MediaSource mediaSource = buildMediaSource(urlName); + mExoPlayer.setMediaItem(MediaItem.fromUri(urlName)); + mExoPlayer.prepare(); + mExoPlayer.play(); + } + }); + } + + public void playExoWithItem(final MediaItem item) { + runOnUiThread(new Runnable() { + @Override + public void run() { + mExoPlayer.setMediaItem(item); + mExoPlayer.prepare(); + mExoPlayer.play(); + } + }); + } + + public void stopExoPlayer() { + runOnUiThread(new Runnable() { + @Override + public void run() { + mExoPlayer.stop(); + //mExoPlayer.release(); + } + }); + } + + public void playAudioPath(String urlName) { +// try { +// mMediaPlayer.reset(); +// mMediaPlayer.setDataSource(urlName); +// mMediaPlayer.prepare(); +// mMediaPlayer.start(); +// } catch (IOException e) { +// e.printStackTrace(); +// } + + playExoWithUrl(urlName); + /* + File file = new File(urlName); + Uri fileUri = Uri.fromFile(file); + MediaItem item = MediaItem.fromUri(fileUri); + //mExoPlayer.setMediaSource(urlName); + mExoPlayer.setMediaItem(item); + mExoPlayer.prepare(); + mExoPlayer.play(); + */ + } + + @Override + public void run() { + if(mPlayMode.equalsIgnoreCase("asset")) { + playAssetAudio(mPlayTarget); + return; + } + + if(mPlayMode.equalsIgnoreCase("path")) { + playAudioPath(mPlayTarget); + return; + } + + if(mTTSPath == null) + return; + + mIsAlive = true; + if(!mIsDeviceOSIC10) { + setMainPlayerVolume(0); + } else { + setSpeakerVolume(mPlayVolume); + String volume = SystemUtil.getConfigValue("dacvolume"); + if(volume.length() == 0) { + mLastDacVolume = 100; + } + } + + boolean allowUntrusted = true; + OkHttpClient client = new OkHttpClient(); + OkHttpClient.Builder clientBuilder = client.newBuilder().readTimeout(10, TimeUnit.SECONDS); + if(allowUntrusted) { + final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + X509Certificate[] cArrr = new X509Certificate[0]; + return cArrr; + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, + final String authType) throws CertificateException { + } + + @Override + public void checkClientTrusted(final X509Certificate[] chain, + final String authType) throws CertificateException { + } + }}; + + + try { + SSLContext sslContext = SSLContext.getInstance("SSL"); + + sslContext.init(null, trustAllCerts, new SecureRandom()); + clientBuilder.sslSocketFactory(sslContext.getSocketFactory()); + + HostnameVerifier hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + Log.d(TAG, "Trust Host :" + hostname); + return true; + } + }; + clientBuilder.hostnameVerifier( hostnameVerifier); + } catch(Exception e) { + e.printStackTrace(); + } + } + + OkHttpClient httpClient = clientBuilder.build(); + CallbackToDownloadFile cb = new CallbackToDownloadFile("/mnt/obb", "tts.wav"); + + Request request = new Request.Builder() + .url(mTTSPath) + .build(); + + httpClient.newCall(request).enqueue(cb); + + /*int downloadId = PRDownloader.download(mTTSPath, "/mnt/obb", "tts.wav") + .build() + .setOnStartOrResumeListener(new OnStartOrResumeListener() { + @Override + public void onStartOrResume() { + + } + }) + .setOnPauseListener(new OnPauseListener() { + @Override + public void onPause() { + + } + }) + .setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel() { + + } + }) + .setOnProgressListener(new OnProgressListener() { + @Override + public void onProgress(Progress progress) { + + } + }) + .start(new OnDownloadListener() { + public void onDownloadComplete() { + Log.i(TAG, "TTS Download Complete"); + mDownloadStatus = DOWNLOAD_STATUS_SUCCESS; + } + + @Override + public void onError(Error error) { + Log.w(TAG, "Error while TTS Download!"); + mDownloadStatus = DOWNLOAD_STATUS_FAILED; + } + });*/ + + while(mDownloadStatus == DOWNLOAD_STATUS_PROCESS) { + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if(!mIsAlive) { + setTTSViewVisibility(false); + setMainPlayerVolume(mVlcMaxVolume); + return; + } + + if(mDownloadStatus == DOWNLOAD_STATUS_FAILED) { + setTTSViewVisibility(false); + setMainPlayerVolume(mVlcMaxVolume); + return; + } + + if(mIsDeviceOSIC10) + { + mIsRitoPlayerOver = false; + mPlayer.openUrl(0, "/system/media/upchimes.wav"); + while (!mIsRitoPlayerOver) { + try { + sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (!mIsAlive) { + mPlayer.stop(); + setTTSViewVisibility(false); + return; + } + } + + mProgress = 0; + while (mProgress < mLastProgress) + { + int curProgress = mProgress; + mIsRitoPlayerOver = false; + mPlayer.openUrl(0, "/mnt/obb/tts.wav"); + while (!mIsRitoPlayerOver) { + try { + sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (!mIsAlive) { + mPlayer.stop(); + setTTSViewVisibility(false); + return; + } + } + mProgress++; + } + + mIsRitoPlayerOver = false; + mPlayer.openUrl(0, "/system/media/downchimes.wav"); + while (!mIsRitoPlayerOver) { + try { + sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (!mIsAlive) { + mPlayer.stop(); + setTTSViewVisibility(false); + return; + } + } + + setTTSViewVisibility(false); + } else { + + playAssetAudio("upchimes.wav"); + + while (!mChimeUpDone) { + try { + sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (!mIsAlive) { +// mMediaPlayer.stop(); +// mMediaPlayer.release(); + + stopExoPlayer(); + setTTSViewVisibility(false); + setMainPlayerVolume(mVlcMaxVolume); + return; + } + } + + + while (mProgress < mLastProgress) { + int curProgress = mProgress; + stopExoPlayer(); + //mMediaPlayer.reset(); +// try { +// mMediaPlayer.setDataSource("/mnt/obb/tts.wav"); +// mMediaPlayer.prepare(); +// mMediaPlayer.start(); +// } catch (IOException e) { +// e.printStackTrace(); +// } + playAudioPath("/mnt/obb/tts.wav"); + + while (curProgress == mProgress) { + try { + sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (!mIsAlive) { + //mMediaPlayer.stop(); + stopExoPlayer(); + //mMediaPlayer.release(); + setTTSViewVisibility(false); + setMainPlayerVolume(mVlcMaxVolume); + return; + } + } + + try { + sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +// try { +// AssetFileDescriptor afd = getAssets().openFd("downchimes.wav"); +// mMediaPlayer.reset(); +// mMediaPlayer.setDataSource(afd); +// afd.close(); +// mMediaPlayer.prepare(); +// mMediaPlayer.start(); +// } catch (IOException e) { +// e.printStackTrace(); +// } + playAssetAudio("downchimes.wav"); + + while (!mChimeDownDone) { + try { + sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (!mIsAlive) { + stopExoPlayer(); +// mMediaPlayer.stop(); +// mMediaPlayer.release(); + setTTSViewVisibility(false); + setMainPlayerVolume(mVlcMaxVolume); + return; + } + } + + stopExoPlayer(); +// mMediaPlayer.stop(); +// mMediaPlayer.release(); + + setTTSViewVisibility(false); + setMainPlayerVolume(mVlcMaxVolume); + } + } + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) + { + Log.w(TAG, "keyUpCode : " + keyCode); + if(event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_12) { // VFD KEY OKAY + long keyTime = SystemClock.uptimeMillis(); + if(keyTime - mKeyPressTime > 2000) { + //Log.w(TAG, "keyLongUpCode : " + keyCode); + //mVfdManager.cancelVfdChangeMode(); + //Log.w(TAG, "Vfd Change Mode Cancelled!"); + } else { + if(!mVfdManager.mVfdChangeMode) + mVfdManager.toggleVfdChangeMode(); + else + mVfdManager.cancelVfdChangeMode(); // short OKAY Key -> Cancel + //Log.w(TAG, "keyUpCode : " + keyCode); +// if(mVfdManager.mVfdConfirmMode) { +// mVfdManager.confirmSettingValue(); +// mVfdManager.mVfdChangeMode = false; +// mVfdManager.mVfdConfirmMode = false; +// } else { +// mVfdManager.toggleVfdChangeMode(); +// } + } + mKeyPressTime = 0; + return true; + } else if(event.getKeyCode() == KeyEvent.KEYCODE_CHANNEL_UP) { // CHANNEL-UP + if(mJsonScheduler != null) { + mJsonScheduler.advancePlayNext(1); + } + return true; + } else if(event.getKeyCode() == KeyEvent.KEYCODE_CHANNEL_DOWN) { // CHANNEL-DOWN + if(mJsonScheduler != null) { + mJsonScheduler.advancePlayNext(-1); + } + return true; + } + return super.onKeyUp(keyCode, event); + } + + public void sendMqttMsg(String topic, String message) { + Log.w("MQTT", "[SEND] <" + topic + "> " + message); + MqttMessage mqttMsg = new MqttMessage(message.getBytes()); + try { + mqttAndroidClient.publish(topic, mqttMsg); + } catch (MqttException e) { + throw new RuntimeException(e); + } + } + + public void handleDsrConfirm() { + try { + JSONObject topObject = new JSONObject(); + topObject.put("status", "confirm"); + topObject.put("ward_id", mWardId); + topObject.put("dsr_seq", mDsrSeq); + + sendMqttMsg("ebcast/device/notify", topObject.toString()); + mDsrSeq = ""; + } catch(Exception e) { + + } + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if(event.getKeyCode() != 199) { + Log.w(TAG, "keyCode : " + keyCode); + } + + if(mWardId != null && mWardId.length() > 0 && mDsrSeq != null && mDsrSeq.length() > 0) { + if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) { // 지령 확인 + handleDsrConfirm(); + } + } + + if(event.getKeyCode() == 200) { // VFD KEY LEFT + //mVfdManager.vfdDrawString(getIPAddress(), 1, mVfdManager.VFD_ALIGN_CENTER); + mVfdManager.moveSettingMode(-1); + return true; + } else if(event.getKeyCode() == 201) { // VFD KEY RIGHT + mVfdManager.moveSettingMode(1); + return true; + } else if(event.getKeyCode() == 202) { // VFD KEY UP + mVfdManager.changeSettingValue(1); + return true; + } else if(event.getKeyCode() == 203) { // VFD KEY DOWN + mVfdManager.changeSettingValue(-1); + return true; + } else if(event.getKeyCode() == 199) { // VFD KEY OKAY + //mVfdManager.toggleVfdChangeMode(); + if(mKeyPressTime == 0) { + mKeyPressTime = SystemClock.uptimeMillis(); + } else { + if(SystemClock.uptimeMillis() - mKeyPressTime > 2000) { + //mVfdManager.cancelVfdChangeMode(); + if(mVfdManager.mVfdChangeMode) + mVfdManager.toggleVfdChangeMode(); + } + } + return true; + } else if(event.getKeyCode() == 183) { // SETUP KEY + Intent intent = new Intent(this, SettingActivity.class); + startActivity(intent); + return true; + } else if(event.getKeyCode() == KeyEvent.KEYCODE_F12) { // Volume-Up + String volume = SystemUtil.getConfigValue("spkvolume"); + if(volume.length() == 0) { + volume = "100"; + } else { + int volumeInt = Integer.parseInt(volume); + volumeInt++; + volume = "" + volumeInt; + } + SystemUtil.setConfigValue("spkvolume", volume); + return true; + } else if(event.getKeyCode() == KeyEvent.KEYCODE_F11) { // Volume-Down + String volume = SystemUtil.getConfigValue("spkvolume"); + if(volume.length() == 0) { + volume = "99"; + } else { + int volumeInt = Integer.parseInt(volume); + volumeInt--; + if(volumeInt < 0) + volumeInt = 0; + volume = "" + volumeInt; + } + SystemUtil.setConfigValue("spkvolume", volume); + return true; + + } else if(event.getKeyCode() == KeyEvent.KEYCODE_PROG_YELLOW) { // Video-Volume-down + String volume = SystemUtil.getConfigValue("videovolume"); + if(volume.length() == 0) { + volume = "11"; + } else { + int volumeInt = Integer.parseInt(volume); + volumeInt--; + if(volumeInt < 1) + volumeInt = 1; + volume = "" + volumeInt; + } + SystemUtil.setConfigValue("videovolume", volume); + return true; + } else if(event.getKeyCode() == KeyEvent.KEYCODE_PROG_BLUE) { // Volume-Volume-Up + String volume = SystemUtil.getConfigValue("videovolume"); + if(volume.length() == 0) { + volume = "13"; + } else { + int volumeInt = Integer.parseInt(volume); + volumeInt++; + if(volumeInt >= 15) + volumeInt = 15; + volume = "" + volumeInt; + } + SystemUtil.setConfigValue("videovolume", volume); + return true; + } + + return false; + } + + public void setWaitViewVisibility(final boolean show) { + final ImageView view = (ImageView)findViewById(R.id.imageViewWait); + if(show) { + if(view.getAlpha() == 0.0f) { + runOnUiThread(new Runnable() { + @Override + public void run() { + view.setAlpha(1.0f); + } + }); + } + } else { + if(view.getAlpha() != 0.0f) { + runOnUiThread(new Runnable() { + @Override + public void run() { + view.setAlpha(0.0f); + } + }); + } + } +// runOnUiThread(new Runnable() { +// @Override +// public void run() { +// if(show) { +// view.setAlpha(1.0f); +// } else { +// view.setAlpha(0.0f); +// } +// } +// }); + } + + public void setTTSViewVisibility(final boolean show) { + if(mIsDeviceOSIC10) { + mIsOnTTSPlaying = show; + return; + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + ImageView view = (ImageView)findViewById(R.id.imageViewTts); + if(show) { + view.setAlpha(1.0f); + mIsOnTTSPlaying = true; + } else { + view.setAlpha(0.0f); + mIsOnTTSPlaying = false; + } + } + }); + } + + public void setWebViewUrl(String url) { + WebView view = (WebView)findViewById(R.id.webview); + view.loadUrl(url); + } + + public void setWebViewVisibility(final boolean show) { + runOnUiThread(new Runnable() { + @Override + public void run() { + WebView view = (WebView)findViewById(R.id.webview); + if(show) { + view.setVisibility(View.VISIBLE); + view.setAlpha(1.0f); + } else { + view.setAlpha(0.0f); + } + } + }); + } + + + public void setWebViewLoadingVisibility(final boolean show) { + final ImageView view = (ImageView)findViewById(R.id.imageViewLoading); + if(show) { + if(view.getAlpha() == 0.0f) { + runOnUiThread(new Runnable() { + @Override + public void run() { + view.setAlpha(1.0f); + } + }); + } + } else { + if(view.getAlpha() != 0.0f) { + runOnUiThread(new Runnable() { + @Override + public void run() { + view.setAlpha(0.0f); + } + }); + } + } + } + + + + public void setMainPlayerVolume(int volume) { + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + if(mVlcPlayer != null) { + mVlcVolume = volume; + mVlcPlayer.setVolume(volume); + //Log.i(TAG, "VLC Volume Setting : " + volume); + + if(mIsDeviceOSIC10) { + AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); + if (volume == 0) { + //audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0); + audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_MUTE, 0); + } else { + audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_UNMUTE, 0); + } + } + } + } + } + + + /* 표시 버전 1 */ + public void handleNotifyScreenOrder(String msgType, final String msgDatetime, final String msgAddress, final String msgDesc, String msgVehicle) { + final String msgTypeShow; + final Bitmap bitmap; + if(msgType.equalsIgnoreCase("fire")) { + bitmap = BitmapFactory.decodeFile("/mnt/sdcard/osic/fire.png"); + shellCommand("miscctrl function_led red on"); + msgTypeShow = "화재"; + } else if(msgType.equalsIgnoreCase("emergency")) { + bitmap = BitmapFactory.decodeFile("/mnt/sdcard/osic/emer.png"); + shellCommand("miscctrl function_led green on"); + msgTypeShow = "구급"; + } else if(msgType.equalsIgnoreCase("rescue")) { + bitmap = BitmapFactory.decodeFile("/mnt/sdcard/osic/rescue.png"); + shellCommand("miscctrl function_led red on"); + shellCommand("miscctrl function_led green on"); + msgTypeShow = "구조"; + } else { + bitmap = BitmapFactory.decodeFile("/mnt/sdcard/osic/general.png"); + msgTypeShow = "일반"; + shellCommand("miscctrl function_led blue on"); + } + + final String msgVehicleShow = msgVehicle.replace('|', '\n'); + + runOnUiThread(new Runnable() { + @Override + public void run() { + ImageView imageView = findViewById(R.id.imageViewCmdBack); + imageView.setImageBitmap(bitmap); + imageView.setVisibility(View.VISIBLE); + SurfaceView surfaceView = findViewById(R.id.surfaceViewCmd); + surfaceView.setVisibility(View.VISIBLE); + surfaceView.setZOrderOnTop(true); + surfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT); + Canvas canvas = surfaceView.getHolder().lockCanvas(); + if(canvas != null) { + float textSize = 50; + float textBaseX = 470; + float textBaseY = 145; + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setColor(Color.BLACK); + //paint.setTextSize(48); + paint.setTextSize(textSize); + paint.setFakeBoldText(true); + paint.setTextAlign(Paint.Align.LEFT); +// Rect bounds = new Rect(); +// paint.getTextBounds(text, 0, text.length(), bounds); +// // Calculate the desired size as a proportion of our testTextSize. +// float desiredTextSize = testTextSize * desiredWidth / bounds.width(); +// // Set the paint for that size. +// paint.setTextSize(desiredTextSize); + + float gap = 92; + //canvas.drawText("종별종별종별", 470, 160, paint); + canvas.drawText(msgTypeShow, textBaseX, textBaseY - ((paint.descent() + paint.ascent()) / 2), paint); + canvas.drawText(msgDatetime, textBaseX, textBaseY - ((paint.descent() + paint.ascent()) / 2) + gap, paint); + canvas.drawText(msgAddress, textBaseX, textBaseY - ((paint.descent() + paint.ascent()) / 2) + gap * 2, paint); + + //canvas.drawText(msgDesc, textBaseX, textBaseY - ((paint.descent() + paint.ascent()) / 2) + gap * 3, paint); + textSize = 50; + do { + TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(Color.BLACK); + textPaint.setTextSize(textSize); + textPaint.setFakeBoldText(true); + textPaint.setTextAlign(Paint.Align.LEFT); + StaticLayout textLayout = new StaticLayout(msgDesc, textPaint, 930, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if(textLayout.getHeight() > 180) { + textSize -= 1; + continue; + } + canvas.save(); + canvas.translate(textBaseX, textBaseY + gap * 3 - 25); + textLayout.draw(canvas); + canvas.restore(); + break; + } while(true); + + //canvas.drawText(msgVehicleShow, textBaseX, textBaseY - ((paint.descent() + paint.ascent()) / 2) + gap * 5 + 10, paint); + textSize = 50; + do { + TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(Color.BLACK); + textPaint.setTextSize(textSize); + textPaint.setFakeBoldText(true); + textPaint.setTextAlign(Paint.Align.LEFT); + StaticLayout textLayout = new StaticLayout(msgVehicleShow, textPaint, 930, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if(textLayout.getHeight() > 370) { + textSize -= 1; + continue; + } + canvas.save(); + canvas.translate(textBaseX, textBaseY + gap * 5 - 25); + textLayout.draw(canvas); + canvas.restore(); + break; + } while(true); + + surfaceView.getHolder().unlockCanvasAndPost(canvas); + } else { + Log.w(TAG, "Canvas is null!"); + } + findViewById(R.id.layoutCommand).setVisibility(View.VISIBLE); + //findViewById(R.id.layoutCommand).setAlpha(1.0f); + mNotifyMsgStartTime = SystemClock.uptimeMillis(); + mIsOnNotifyMsg = true; + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + if(mVlcPlayer != null) { + setMainPlayerVolume(0); + } + } + //bitmap.recycle(); +// findViewById(R.id.layoutCommand).bringToFront(); +// findViewById(R.id.layoutCommand).setZ(500); + } + }); + + } + + + /* 표시 버전 2 */ + public void handleNotifyScreenOrder(String msgType, + final String msgDsrClass, + final String msgDsrSize, + final String msgDatetime, + final String msgAddressDoro, + final String msgAddressJibun, + final String msgDesc, + String msgVehicle) + { + final String msgTypeShow; + final Bitmap bitmap; + if(msgType.equalsIgnoreCase("fire")) { + bitmap = BitmapFactory.decodeFile("/mnt/sdcard/osic/fire.png"); + shellCommand("miscctrl function_led green off"); + shellCommand("miscctrl function_led blue off"); + shellCommand("miscctrl function_led red on"); + } else if(msgType.equalsIgnoreCase("emergency")) { + bitmap = BitmapFactory.decodeFile("/mnt/sdcard/osic/emer.png"); + shellCommand("miscctrl function_led red off"); + shellCommand("miscctrl function_led blue off"); + shellCommand("miscctrl function_led green on"); + } else if(msgType.equalsIgnoreCase("rescue")) { + bitmap = BitmapFactory.decodeFile("/mnt/sdcard/osic/rescue.png"); + shellCommand("miscctrl function_led blue off"); + shellCommand("miscctrl function_led red on"); + shellCommand("miscctrl function_led green on"); + } else { + bitmap = BitmapFactory.decodeFile("/mnt/sdcard/osic/general.png"); + shellCommand("miscctrl function_led red off"); + shellCommand("miscctrl function_led green off"); + shellCommand("miscctrl function_led blue on"); + } + + final String msgVehicleShow = msgVehicle.replace("|", ", "); + + runOnUiThread(new Runnable() { + @Override + public void run() { + ImageView imageView = findViewById(R.id.imageViewCmdBack); + imageView.setImageBitmap(bitmap); + imageView.setVisibility(View.VISIBLE); + SurfaceView surfaceView = findViewById(R.id.surfaceViewCmd); + surfaceView.setVisibility(View.VISIBLE); + + new Thread() { + @Override + public void run() { + try { + SurfaceView surfaceView = findViewById(R.id.surfaceViewCmd); + surfaceView.setZOrderOnTop(true); + surfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT); + Canvas canvas = surfaceView.getHolder().lockCanvas(); + if (canvas != null) { + float textClassSize = 96; + float textClassBaseX = 1430; + float textClassBaseY = 125; + float textSize = 46; + float textBaseX = 360; + float textBaseY = 215; + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + + paint.setFakeBoldText(true); + paint.setTextAlign(Paint.Align.CENTER); + paint.setColor(Color.WHITE); + do { + TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(Color.BLACK); + textPaint.setTextSize(textClassSize); + textPaint.setTextAlign(Paint.Align.LEFT); + float textWidth = textPaint.measureText(msgDsrClass); + if (textWidth > 910) { + textClassSize -= 1; + continue; + } + break; + } while (true); + paint.setTextSize(textClassSize); + canvas.drawText(msgDsrClass, textClassBaseX, textClassBaseY, paint); + + paint.setTextAlign(Paint.Align.LEFT); + paint.setColor(Color.BLACK); + paint.setTextSize(textSize); + + float gap = 75; + canvas.drawText(msgDatetime, textBaseX, textBaseY - ((paint.descent() + paint.ascent()) / 2), paint); + canvas.drawText(msgDsrSize, textBaseX + 930, textBaseY - ((paint.descent() + paint.ascent()) / 2), paint); + canvas.drawText(msgAddressDoro, textBaseX, textBaseY - ((paint.descent() + paint.ascent()) / 2) + gap, paint); + canvas.drawText(msgAddressJibun, textBaseX, textBaseY - ((paint.descent() + paint.ascent()) / 2) + gap * 2, paint); + + textSize = 50; + do { + TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(Color.BLACK); + textPaint.setTextSize(textSize); + textPaint.setFakeBoldText(true); + textPaint.setTextAlign(Paint.Align.LEFT); + StaticLayout textLayout = new StaticLayout(msgDesc, textPaint, 1200, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (textLayout.getHeight() > 320) { + textSize -= 1; + continue; + } + int layoutHeight = textLayout.getHeight(); + canvas.save(); + canvas.translate(textBaseX, textBaseY + gap * 3 - 30 + (320 - layoutHeight) / 2); + textLayout.draw(canvas); + canvas.restore(); + break; + } while (true); + + //canvas.drawText(msgVehicleShow, textBaseX, textBaseY - ((paint.descent() + paint.ascent()) / 2) + gap * 5 + 10, paint); + textSize = 50; + do { + TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(Color.BLACK); + textPaint.setTextSize(textSize); + textPaint.setFakeBoldText(true); + textPaint.setTextAlign(Paint.Align.LEFT); + //StaticLayout textLayout = new StaticLayout(msgVehicleShow, textPaint, 530, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + StaticLayout.Builder builder = StaticLayout.Builder.obtain(msgVehicleShow, 0, msgVehicleShow.length(), textPaint, 1200) + .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .setLineSpacing(0.0f, 1.0f) + .setIncludePad(false) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .setBreakStrategy(LineBreaker.BREAK_STRATEGY_SIMPLE) + .setMaxLines(5); + StaticLayout textLayout = builder.build(); + + + if (textLayout.getHeight() > 200) { + textSize -= 1; + continue; + } + int layoutHeight = textLayout.getHeight(); + canvas.save(); + canvas.translate(textBaseX, textBaseY + gap * 7 + 10 + (210 - layoutHeight) / 2); + textLayout.draw(canvas); + canvas.restore(); + break; + } while (true); + + surfaceView.getHolder().unlockCanvasAndPost(canvas); + + Log.w(TAG, "======================"); + Log.w(TAG, " 지령표출화면 표시시작 "); + Log.w(TAG, "======================"); + } else { + Log.w(TAG, "Canvas is null!"); + } + } catch(Exception e) { + e.printStackTrace(); + } + } + }.start(); + + findViewById(R.id.layoutCommand).setVisibility(View.VISIBLE); + mNotifyMsgStartTime = SystemClock.uptimeMillis(); + mIsOnNotifyMsg = true; + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + if(mVlcPlayer != null) { + setMainPlayerVolume(0); + } + } + } + }); + + + } + + public void doStoredScheduleUpdate(String target) { + if(target.equalsIgnoreCase("ip")) { + JsonResult jsonResult = JsonManager.loadJsonData("ip-schedule.json"); + if (jsonResult != null) { + MainActivity.INSTANCE.mJsonScheduler.updateJsonTargetResult("ip", jsonResult); + } + } else if(target.equalsIgnoreCase("file")) { + JsonResult jsonResult = JsonManager.loadJsonData("file-schedule.json"); + if (jsonResult != null) { + MainActivity.INSTANCE.mJsonScheduler.updateJsonTargetResult("file", jsonResult); + } + } + } + + private void setupVlcMediaOption(Media m) { + String optValue = SystemUtil.property_get("sys.rito.net.cache"); + if(optValue == null || optValue.length() == 0) + optValue = "" + VLC_NETWORK_CACHE_DEFAULT; + m.addOption(":network-caching=" + optValue); + + optValue = SystemUtil.property_get("sys.rito.live.cache"); + if(optValue == null || optValue.length() == 0) + optValue = "" + VLC_LIVE_CACHE_DEFAULT; + m.addOption(":live-caching=" + optValue); + + optValue = SystemUtil.property_get("sys.rito.udp.buffer"); + if(optValue == null || optValue.length() == 0) + optValue = "" + VLC_UDP_BUFFER_DEFAULT; + m.addOption(":udp-buffer=" + optValue); + + m.addOption(":clock-jitter=0"); + m.addOption(":clock-synchro=0"); + } + + public void nativeNotify(int code, String info) { + Log.w(TAG, "NOTIFY Code : " + code + ", Info : " + info); + if(code == MANAGER_NOTIFY_ORDER_START_PLAYBACK) { + + if(mIsDeviceOSIC10) { + if (SystemUtil.APP_MODE.equals("active")) { // Browser Mode 인 경우 + return; // 서버로 부터 재생 명령 무시 + } + } + + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_RITO) { + mPlayer.stop(); + String[] cutInfo = info.split(" "); + if (cutInfo[0].startsWith("mc://")) { + cutInfo[0] = cutInfo[0].replace("mc://", "udp://"); + cutInfo[0] = cutInfo[0].replace("_", ":"); + } + SurfaceView view = findViewById(R.id.playerView); + mPlayer.mSurfaceView = view; + mPlayer.openUrl(0, cutInfo[0]); + } else if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + String[] cutInfo = info.split(" "); + if (cutInfo[0].startsWith("mc://")) { + cutInfo[0] = cutInfo[0].replace("mc://", "udp://"); +// if(!cutInfo[0].contains("@")) { +// cutInfo[0] = cutInfo[0].replace("udp://", "udp://@"); +// } + cutInfo[0] = cutInfo[0].replace("_", ":").trim(); + String[] partSplit = cutInfo[0].substring(6).split(":"); + if(!partSplit[0].startsWith("2")) { + cutInfo[0] = "udp://@:" + partSplit[1]; + } else { + cutInfo[0] = "udp://@" + partSplit[0] + ":" + partSplit[1]; + } + } else if (cutInfo[0].startsWith("hsact://")) { + cutInfo[0] = cutInfo[0].replace("hsact://", "rtsp://").trim(); + } + Log.w(TAG, "VLCPlayer URL : " + cutInfo[0]); + mLastPlayUrl = cutInfo[0]; + if(mCurrentPlayUrl == null || !cutInfo[0].equals(mCurrentPlayUrl)) { + mVlcPlayer.stop(); + Media m = new Media(mVLC, Uri.parse(cutInfo[0])); + if(mIsDeviceOSIC10) { + setupVlcMediaOption(m); + } + mVlcPlayer.setMedia(m); + mVLCMedia = m; + mVlcPlayer.play(); + mCurrentPlayUrl = cutInfo[0]; + } + } + setTTSViewVisibility(false); + setWaitViewVisibility(false); + } else if(code == MANAGER_NOTIFY_ORDER_STOP_PLAYBACK) { + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_RITO) { + mPlayer.stop(); + } else if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + if(mIsDeviceOSIC10) { + mLastPlayUrl = null; + } + mVlcPlayer.stop(); + try { + AssetFileDescriptor afd = getAssets().openFd("blackground.png"); + mVlcPlayer.play(afd); + } catch(Exception e) { + } + } + + if(!SystemUtil.APP_MODE.equals("active")) { // Browser Mode 아닌경우 + setWaitViewVisibility(true); + } + + /*MediaPlayer player = MediaPlayer.create(this, R.raw.upchimes); + player.start();*/ + /*MediaPlayer player2 = MediaPlayer.create(this, R.raw.downchimes); + player2.start();*/ + + } else if(code == MANAGER_NOTIFY_ORDER_START_TTS) { + if(mTTSPlayer != null) { + mTTSPlayer.terminate(); + } + mTTSPlayer = new AudioFilePlayer(); + mTTSPlayer.resetPlayer(); + + String[] tokenList = info.split("\r\n"); + for(String token : tokenList) { + String[] split = token.split("="); + if(split.length == 2) { + if(split[0].equalsIgnoreCase("URL")) { + mTTSPlayer.setDownloadPath(split[1]); + } else if(split[0].equalsIgnoreCase("COUNT")) { + mTTSPlayer.setPlayCount(Integer.parseInt(split[1])); + } else if(split[0].equalsIgnoreCase("TTS_VOLUME")) { + mTTSPlayer.setPlayVolume(Integer.parseInt(split[1])); + } + } + } + + /* + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_RITO) { + mPlayer.stop(); + } else if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + mVlcPlayer.stop(); + } + */ + + setTTSViewVisibility(true); + + + mTTSPlayer.start(); + } else if(code == MANAGER_NOTIFY_ORDER_CEC_CONTROL) { + if(info.equalsIgnoreCase("on")) { + Intent intent = new Intent("kr.co.rito.factory.setting"); + //intent.setClassName("kr.co.rito.factory", "SettingChanger"); + intent.setComponent(new ComponentName("kr.co.rito.factory", "kr.co.rito.factory.SettingChanger")); + intent.putExtra("TYPE", "CecControl"); + intent.putExtra("ORDER", "active"); + sendBroadcast(intent); + } else if(info.equalsIgnoreCase("off")) { + Intent intent = new Intent("kr.co.rito.factory.setting"); + intent.setComponent(new ComponentName("kr.co.rito.factory", "kr.co.rito.factory.SettingChanger")); + intent.putExtra("TYPE", "CecControl"); + intent.putExtra("ORDER", "standby"); + sendBroadcast(intent); + } + } else if(code == MANAGER_NOTIFY_ORDER_START_TICKER) { + String _text = ""; + int _x, _y, _width, _height; + int _fcolor, _bcolor, _count; + int _speed; + _x = _y = _width = _height = _fcolor = _bcolor = _count = _speed = 0; + + String[] tokenList = info.split("\r\n"); + for(String token : tokenList) { + String[] split = token.split("="); + if(split.length == 2) { + if(split[0].equalsIgnoreCase("X")) { + _x = Integer.parseInt(split[1]); + } else if(split[0].equalsIgnoreCase("Y")) { + _y = Integer.parseInt(split[1]); + } else if(split[0].equalsIgnoreCase("WIDTH")) { + _width = Integer.parseInt(split[1]); + } else if(split[0].equalsIgnoreCase("HEIGHT")) { + _height = Integer.parseInt(split[1]); + } else if(split[0].equalsIgnoreCase("SPEED")) { + _speed = Integer.parseInt(split[1]); + } else if(split[0].equalsIgnoreCase("COUNT")) { + _count = Integer.parseInt(split[1]); + } else if(split[0].equalsIgnoreCase("FORE_COLOR")) { + _fcolor = Integer.parseInt(split[1]); + } else if(split[0].equalsIgnoreCase("BACK_COLOR")) { + _bcolor = Integer.parseInt(split[1]); + } else if(split[0].equalsIgnoreCase("TEXT")) { + _text = split[1]; +// try { +// _text = new String(split[1].getBytes("UTF-8")); +// } catch (UnsupportedEncodingException e) { +// e.printStackTrace(); +// } + } + } + } + + _x = _x * 1920 / 1280; + _y = _y * 1080 / 720; + _width = _width * 1920 / 1280; + _height = _height * 1080 / 720; + + mTickerManager.setupTicker(_text, + new Rect(_x, _y, _x + _width, _y + _height), + _fcolor, _bcolor, _count, (float)_speed); + mTickerManager.renderTicker(); + } else if(code == MANAGER_NOTIFY_ORDER_JSON_ORDER) { + String jsonUrl = null; + String jsonType = null; + String[] tokenList = info.split("\r\n"); + for(String token : tokenList) { + String[] split = token.split("="); + if(split.length == 2) { + if(split[0].equalsIgnoreCase("URL")) { + jsonUrl = split[1]; + } else if(split[0].equalsIgnoreCase("TYPE")) { + jsonType = split[1]; + } + } + } + + Log.w(TAG, "JSON TYPE : " + jsonType + ", JSON URL : " + jsonUrl); + JsonManager jsonManager = new JsonManager(this); + jsonManager.setDownloadPath( "http://" + getServerAddress() + "/" + jsonUrl); + jsonManager.start(); + } else if(code == MANAGER_NOTIFY_ORDER_NOTIFY_SCREEN_ORDER) { + Log.w(TAG, "Got Notify Screen Message : " + info); + String msgType = ""; + String msgDatetime = ""; + String msgAddress = ""; + String msgDesc = ""; + String msgVehicle = ""; + + String msgAddressDoro = ""; + String msgAddressJibun = ""; + String msgDsrClass = ""; + String msgDsrSize = ""; + + String[] tokenList = info.split("\r\n"); + for(String token : tokenList) { + String[] split = token.split("="); + if(split.length == 2) { + if(split[0].equalsIgnoreCase("MSGTYPE")) { + msgType = split[1]; + } else if(split[0].equalsIgnoreCase("DATETIME")) { + msgDatetime = split[1]; + } else if(split[0].equalsIgnoreCase("ADDRESS")) { + msgAddress = split[1]; + } else if(split[0].equalsIgnoreCase("ADDRESS-DORO")) { + msgAddressDoro = split[1]; + } else if(split[0].equalsIgnoreCase("ADDRESS-JIBUN")) { + msgAddressJibun = split[1]; + } else if(split[0].equalsIgnoreCase("CLASS")) { + msgDsrClass = split[1]; + } else if(split[0].equalsIgnoreCase("SIZE")) { + msgDsrSize = split[1]; + } else if(split[0].equalsIgnoreCase("DESC")) { + msgDesc = split[1]; + } else if(split[0].equalsIgnoreCase("VEHICLE")) { + msgVehicle = split[1]; + } + } + } + + /* For Demo Perpose - 2023-09-19 */ + /* + msgType = "general"; + msgDatetime = "2023년 9월 21일"; + msgDsrClass = ""; + msgDsrSize = "메이저"; + msgAddressDoro = "192.168.1.100"; // IP주소 + msgAddressJibun = "본청 방송실"; // 대상기관 + msgDesc = "비정상적 파일시스템 용량 증가 및 네트워크 트래픽 증가"; + msgVehicle = "PC악성 코드 감염 의심|네트워크 분리 및 백신 검사 필요|시스템 로그 분석 필요"; + */ + + if(msgAddress != null && msgAddress.length() > 0) { + handleNotifyScreenOrder(msgType, msgDatetime, msgAddress, msgDesc, msgVehicle); + } else { + handleNotifyScreenOrder(msgType, + msgDsrClass, + msgDsrSize, + msgDatetime, + msgAddressDoro, + msgAddressJibun, + msgDesc, + msgVehicle); + + // Notify Screen Chime + AudioFilePlayer chimePlayer = new AudioFilePlayer(); + chimePlayer.setPlayMode("asset"); + if(msgType.equalsIgnoreCase("fire")) { + chimePlayer.setPlayTarget("fire"); + } else if(msgType.equalsIgnoreCase("emergency")) { + chimePlayer.setPlayTarget("emergency"); + } else if(msgType.equalsIgnoreCase("rescue")) { + chimePlayer.setPlayTarget("rescue"); + } else { + chimePlayer.setPlayTarget("general"); + } + chimePlayer.start(); + } + } else if(code == MANAGER_NOTIFY_ORDER_EVENT_MESSAGE) { + //Log.w(TAG, "Got Event Message : " + info); + String evtType = null; + String evtClassId = null; + String evtBaseUrl = null; + String evtJsonRaw = null; + String[] tokenList = info.split("\r\n"); + for(String token : tokenList) { + String[] split = token.split("="); + if(split.length == 2) { + if(split[0].equalsIgnoreCase("EVENT")) { + evtType = split[1]; + } else if(split[0].equalsIgnoreCase("CLASS_ID")) { + evtClassId = split[1]; + } else if(split[0].equalsIgnoreCase("BASE_URL")) { + evtBaseUrl = split[1]; + } else if(split[0].equalsIgnoreCase("JSON_RAW")) { + evtJsonRaw = split[1]; + } + } + } + + if(evtType.equalsIgnoreCase("image-update")) { + if (mFileDownloader != null) { + String[] downList = {"fire.png", "rescue.png", "emer.png", "general.png"}; + try { + String topClass = evtClassId.substring(0, 2) + "0000"; + for (String target : downList) { + mFileDownloader.addDownloadTarget(evtBaseUrl + "/" + topClass + "-" + target, MainActivity.INSTANCE.mEmmcSavePath, target); + } + } catch(Exception e) { + e.printStackTrace(); + } + } + } else if(evtType.equalsIgnoreCase("channel-update")) { + //Log.w(TAG, "JSON RAW : " + evtJsonRaw); + try { + JSONObject topObject = new JSONObject(evtJsonRaw); + JSONArray jsonArray = topObject.getJSONArray("data"); + for(int i = 0;i < jsonArray.length();i++) { + JSONObject jo = jsonArray.getJSONObject(i); + //Log.w(TAG, "Address : " + jo.getString("address")); + } + + File saveFile = new File(MainActivity.INSTANCE.mSDSavePath + "/ip-schedule.json"); + if(saveFile.exists()) + saveFile.delete(); + + try { + saveFile.createNewFile(); + OutputStream os = new FileOutputStream(saveFile); + byte[] evtData = evtJsonRaw.getBytes("utf-8"); + + os.write(evtData, 0, evtData.length); + os.flush(); + os.close(); + } catch (IOException e) { + } + + MainActivity.INSTANCE.doSync(); + doStoredScheduleUpdate("ip"); + } catch(Exception e) { + e.printStackTrace(); + } + } + } + } + + + @Override + public void onPause() { + terminateService(); + + super.onPause(); + } + + @Override + protected void onStart() { + super.onStart(); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + } + + RitoWebPlayerInterface ritoWebPlayerInterface; + RitoWebSystemInterface ritoWebSystemInterface; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + + setContentView(R.layout.activity_main); + + mChannelOsdView = findViewById(R.id.textViewChannelOsd); + mChannelOsdImageView = findViewById(R.id.imageViewChannelOsd); + mImageViewBlack = findViewById(R.id.imageViewBlack); + mProgressBar = findViewById(R.id.progressBar); + + mCarCastTitleView = findViewById(R.id.textViewCarcast); + + mJsonResult = null; + mJsonIpSchedule = null; + mJsonFileSchedule = null; + INSTANCE = this; + mManagerActMode = MANAGER_ACTION_MODE_STANDBY; + + //SystemUtil.logStart(); + + mIsDeviceOSIC10 = SystemUtil.property_get("rito.device.type").equalsIgnoreCase("osic10"); + if(!mIsDeviceOSIC10) { + ImageView imgView = findViewById(R.id.imageViewWait); + imgView.setImageResource(R.drawable.no_broadcast); + } + + mSDCardPath = SystemUtil.getSDCardPath(); + mEmmcSavePath = Environment.getExternalStorageDirectory().getPath() + "/osic"; + + +// PRDownloaderConfig config = PRDownloaderConfig.newBuilder() +// .setReadTimeout(30_000) +// .setConnectTimeout(30_000) +// .build(); +// PRDownloader.initialize(getApplicationContext(), config); + + mTickerManager = new TickerManager(this, (TextView)findViewById(R.id.textViewTicker)); + mVfdManager = new VfdManager(); + mVfdManager.start(); + + mSystemManager = new SystemManager(); + //mSystemManager.initialize(); + //mSystemManager.activateCpuFan(true); + mSystemManager.start(); + + String fancontrol = getConfigValue("fancontrol"); + if(!fancontrol.equals("0")) { + mCoolerManager = new CoolerManager(); + mCoolerManager.initialize(); + mCoolerManager.start(); + } else { + mCoolerManager = new CoolerManager(); + mCoolerManager.initialize(); + mCoolerManager.activateCpuFan(false); + } + + mFileDownloader = new FileDownloader(this); + mFileDownloader.start(); + + + int minBufferSize = AudioTrack.getMinBufferSize(EBCAST_SAMPLE_RATE, EBCAST_CHANNEL_CONFIG, EBCAST_AUDIO_FORMAT); + mAudioTrack = new AudioTrack( + AudioManager.STREAM_MUSIC, + EBCAST_SAMPLE_RATE, + EBCAST_CHANNEL_CONFIG, + EBCAST_AUDIO_FORMAT, + minBufferSize, + AudioTrack.MODE_STREAM + ); + mAudioTrack.play(); +// mTTSPlayer = new AudioFilePlayer(); +// mTTSPlayer.setDownloadPath("http://192.168.1.60/test.mp3"); +// mTTSPlayer.setPlayCount(3); +// mTTSPlayer.start(); + /*mTTSPlayer.setDownloadPath("http://192.168.1.75/Factory.tgz"); + mTTSPlayer.setDownloadPath("https://support.google.com/websearch/answer/1325808"); + mTTSPlayer.start();*/ + + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_RITO) { + mPlayerNotify = new RitoPlayer.playerNotify() { + @Override + public void notify(int code, String info) { + Log.i("RitoMain", "NOTIFY Code : " + code); + if (code == RitoPlayer.PLAYER_NOTIFY_EOS) { + //mPlayer.stop(); + /*SurfaceView view = findViewById(R.id.surfaceView); + mPlayer.mSurfaceView = view; + mPlayer.openUrl(0, "http://219.255.217.140/ext/contents/Your_wedding.ts");*/ + } + } + }; + + if (mPlayer == null) { + mPlayer = new RitoPlayer(mPlayerNotify, 0); + mPlayer.init(); + } + + if (mAudioPlayer == null) { + mAudioPlayer = new RitoPlayer(mPlayerNotify, 1); + mAudioPlayer.init(); + + SystemUtil.setupAudioOut(1); + mAudioSubOut = new RitoPlayer.AudioOut() { + @Override + public void writeSamples(byte[] bytes, int i) { + if(mLastDacVolume > 0) { + SystemUtil.adjustDataVolume(bytes, i, mLastDacVolume); + //Log.i(TAG, "AudioPlayer AudioOut " + i + " bytes..."); + SystemUtil.writeAudioOut(bytes, i); + } + } + }; + + mAudioPlayer.setAudioOut(mAudioSubOut); + mAudioPlayer.setAudioDirection(RitoPlayer.AUDIO_OUT_DIRECTION_AOUT); + //mAudioPlayer.openUrl(1,"http://211.204.122.90/ext/contents/akdong.mp3"); + } + +// SurfaceView view = findViewById(R.id.playerView); +// mPlayer.mSurfaceView = view; + + mTexturePlayView = findViewById(R.id.texturePlayView); + mTexturePlayView.setSurfaceTextureListener(this); + mPlayer.mTextureView = mTexturePlayView; + mPlayer.openUrl(0,"udp://127.0.0.1:5000"); + + //mPlayer.openUrl(0,"http://211.204.122.90/ext/contents/IU.mp4"); + /*setWaitViewVisibility(false); + mPlayer.openUrl(0,"http://219.255.217.140/ext/contents/IU.mp4");*/ + //mPlayer.openUrl(0,"rtsp://gookmin.iptime.org:17100/Camera19"); + } else if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + mSurface = findViewById(R.id.playerView); + holder = mSurface.getHolder(); + + mTexturePlayView = findViewById(R.id.texturePlayView); + mTexturePlayView.setSurfaceTextureListener(this); + //mTexturePlayView.setVisibility(View.VISIBLE); + + String vlcMaxVolume = SystemUtil.property_get("sys.rito.volup.max"); + if(vlcMaxVolume.length() > 0) { + mVlcMaxVolume = Integer.parseInt(vlcMaxVolume); + } else { + mVlcMaxVolume = 100; + } + + //createPlayer("rtsp://gookmin.iptime.org:17100/Camera19"); + if(USING_PLAYER_VIEW_TYPE == PLAYER_VIEW_TYPE_SURFACE_VIEW) { + createPlayer(null); + } else { + mSurface.setVisibility(View.INVISIBLE); + } + + SystemUtil.setupAudioOut(1); + mAudioSubOut = new RitoPlayer.AudioOut() { + @Override + public void writeSamples(byte[] bytes, int i) { + if(mLastDacVolume > 0) { + SystemUtil.adjustDataVolume(bytes, i, mLastDacVolume); + SystemUtil.writeAudioOut(bytes, i); + } + } + }; + + mPlayerNotify = new RitoPlayer.playerNotify() { + @Override + public void notify(int code, String info) { + Log.i("RitoNotify", "NOTIFY Code : " + code); + if (code == RitoPlayer.PLAYER_NOTIFY_EOS) { + mIsRitoPlayerOver = true; + } + } + }; + + if (mPlayer == null) { + mPlayer = new RitoPlayer(mPlayerNotify); + mPlayer.init(); + mPlayer.setAudioOut(mAudioSubOut); + mPlayer.setAudioDirection(RitoPlayer.AUDIO_OUT_DIRECTION_AOUT); + //mPlayer.openUrl(0,"http://219.255.217.140/ext/contents/akdong.mp3"); + } + + //createPlayer("http://219.255.217.140/ext/contents/IU.mp4"); + //createPlayer("udp://@:1234"); + //createPlayer("udp://@:5000"); + //createPlayer("http://172.16.40.146/20211118044730.ts"); + //createPlayer("http://192.168.1.20/20211118044730.ts"); + //createPlayer("udp://@234.6.7.8:5000"); + + } + setTTSViewVisibility(false); + setWaitViewVisibility(true); + //setWaitViewVisibility(false); + + mExoPlayer = new ExoPlayer.Builder(MainActivity.INSTANCE).build(); + mExoPlayer.addListener(new Player.Listener() { + @Override + public void onTimelineChanged( + Timeline timeline, @Player.TimelineChangeReason int reason) { + Object manifest = mExoPlayer.getCurrentManifest(); + if (manifest != null) { + HlsManifest hlsManifest = (HlsManifest) manifest; + // Do something with the manifest. + } + } + + @Override + public void onPlayerError(PlaybackException error) { + // 오류 처리 + switch (error.errorCode) { + case PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED: + // 네트워크 연결 오류 처리 + Log.e("ExoPlayer", "Network connection error: " + error.getMessage()); + break; + + case PlaybackException.ERROR_CODE_DECODING_FAILED: + // 디코딩 오류 처리 + Log.e("ExoPlayer", "Decoding error: " + error.getMessage()); + break; + + default: + // 그 외의 오류 처리 + Log.e("ExoPlayer", "Playback error: " + error.getMessage()); + break; + } + } + + @Override + public void onPlaybackStateChanged(int playbackState) { + if (playbackState == Player.STATE_ENDED) { + // 재생 완료 이벤트 처리 + + } + } + }); + + mWardId = getConfigValue("ward_id"); + mEbcastMqttIp = getConfigValue("ebcast_ip"); + String app_mode = getConfigValue("app_mode"); + SystemUtil.APP_MODE = app_mode; + if(mIsDeviceOSIC10) { + if (SystemUtil.APP_MODE.equals("active")) { // Browser Mode 인 경우 + setWaitViewVisibility(false); + WebView webview = (WebView) findViewById(R.id.webview); + webview.setWebViewClient(new RitoWebViewClient()); + webview.getSettings().setJavaScriptEnabled(true); + + ritoWebPlayerInterface = new RitoWebPlayerInterface(); + ritoWebSystemInterface = new RitoWebSystemInterface(); + webview.addJavascriptInterface(ritoWebSystemInterface, "system"); + webview.addJavascriptInterface(ritoWebPlayerInterface, "player"); + String value = SystemUtil.getConfigValue("svraddr"); + //setWebViewUrl("http://" + value + "/stbpage/"); + setWebViewUrl("http://" + value + "/stbpage/osic10.php"); // modified by ritoseo - 2023-05-21 + setWebViewVisibility(true); + setWebViewLoadingVisibility(true); + } + } + + if(mSDCardPath != null) { + mSDSavePath = mSDCardPath + "/osic"; + File directory = new File(mSDSavePath); + if(!directory.exists()) + directory.mkdir(); + } + + if(mJsonScheduler == null) { + mJsonScheduler = new JsonScheduler(); + mJsonScheduler.start(); + } + + if(mDacScheduler == null) { + mDacScheduler = new DacScheduler(); + mDacScheduler.start(); + } + + doStoredScheduleUpdate("ip"); + doStoredScheduleUpdate("file"); + +// Log.w("RITO", "mJsonIpSchedule : " + mJsonIpSchedule); +// Log.w("RITO", "mJsonFileSchedule : " + mJsonFileSchedule); + + String versionString = SystemUtil.getVersionInfo(this); + nativeInit(versionString); + + + new Thread() { + @Override + public void run() { +// try { +// sleep(2000); +// } catch (InterruptedException e) { +// } +// +// String msgType = "general"; +// AudioFilePlayer chimePlayer = new AudioFilePlayer(); +// chimePlayer.setPlayMode("asset"); +// if(msgType.equalsIgnoreCase("fire")) { +// chimePlayer.setPlayTarget("fire"); +// } else if(msgType.equalsIgnoreCase("emergency")) { +// chimePlayer.setPlayTarget("emergency"); +// } else if(msgType.equalsIgnoreCase("rescue")) { +// chimePlayer.setPlayTarget("rescue"); +// } else { +// chimePlayer.setPlayTarget("general"); +// } +// chimePlayer.start(); + + if(mEbcastMqttIp == null || mEbcastMqttIp.length() == 0) + return; + + Random random = new Random(); + String macAddr = SystemUtil.property_get("rito.board.mac"); + Log.w(TAG, "Mqtt Address = " + "tcp://" + mEbcastMqttIp + ":1883"); + while (true) + { + try { + if(mqttAndroidClient == null) { + MqttConnectOptions connectOptions; + + //mqttAndroidClient = new MqttAndroidClient(MainActivity.INSTANCE.getApplicationContext(), "tcp://" + mEbcastMqttIp + ":1883", macAddr + random.nextInt()); + mqttAndroidClient = new MqttAndroidClient(MainActivity.INSTANCE.getApplicationContext(), "tcp://" + mEbcastMqttIp + ":1883", macAddr + random.nextInt()); + + if(mqttAndroidClient != null) { + try { + connectOptions = new MqttConnectOptions(); +// connectOptions.setAutomaticReconnect(true); +// connectOptions.setCleanSession(true); + connectOptions.setAutomaticReconnect(false); + connectOptions.setCleanSession(true); + connectOptions.setKeepAliveInterval(30); + + mqttAndroidClient.setCallback(new MqttCallback() { + @Override + public void connectionLost(Throwable cause) { + // 연결이 끊겼을 때 실행 + Log.d("MQTT","Connection lost: " + cause.getMessage()); + if(mqttAndroidClient != null) { + mqttAndroidClient.close(); + } + mqttAndroidClient = null; + } + + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + // 메시지가 도착했을 때 실행 + System.out.println("Message arrived from topic " + topic + ": " + new String(message.getPayload())); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + // 메시지 전송이 완료되었을 때 실행 + //System.out.println("Delivery complete"); + } + }); + + mqttAndroidClient.connect(connectOptions, null, new IMqttActionListener() { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + // 연결 성공 + Log.d("MQTT", "Connected"); + try { + mqttAndroidClient.subscribe("ebcast/notify/#", 1, new IMqttMessageListener() { + public void messageArrived(String topic, MqttMessage message) throws Exception { + String msgValue = message.toString(); + Log.d("MQTT", "[RECV] <" + topic + "> " + msgValue); + if(topic.equalsIgnoreCase("ebcast/notify/screen")) { + JSONObject topObject = new JSONObject(msgValue); + String wardList = topObject.getString("wardList"); + String dsrSeq = topObject.getString("dsrSeq"); + String msgType = topObject.getString("msgType"); + String msgDsrClass = topObject.getString("msgDsrClass"); + String msgDsrSize = topObject.getString("msgDsrSize"); + String msgDatetime = topObject.getString("msgDatetime"); + String msgAddressDoro = topObject.getString("msgAddressDoro"); + String msgAddressJibun = topObject.getString("msgAddressJibun"); + String msgDesc = topObject.getString("msgDesc"); + String msgVehicle = topObject.getString("msgVehicle"); + Log.d("MQTT", "mWardId : " + MainActivity.INSTANCE.mWardId); + Log.d("MQTT", msgType + ", " + msgDsrClass + ", " + msgDsrSize); + if(wardList.contains(MainActivity.INSTANCE.mWardId)) { + mDsrSeq = dsrSeq; + handleNotifyScreenOrder(msgType, msgDsrClass, msgDsrSize, msgDatetime, msgAddressDoro, msgAddressJibun, msgDesc, msgVehicle); + + final String assetPath; + if(msgType.equalsIgnoreCase("fire")) { + assetPath = "file:///android_asset/fire.wav"; + } else if(msgType.equalsIgnoreCase("emergency")) { + assetPath = "file:///android_asset/emergency.wav"; + } else if(msgType.equalsIgnoreCase("rescue")) { + assetPath = "file:///android_asset/rescue.wav"; + } else { + assetPath = "file:///android_asset/general.wav"; + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + try { + MediaSource mediaSource = buildMediaSource(assetPath); + //mExoPlayer.setMediaItem(mediaSource); + mExoPlayer.setMediaSource(mediaSource); + mExoPlayer.prepare(); + mExoPlayer.play(); + } catch(Exception e) { + e.printStackTrace(); + } + } + }); + + // Notify Screen Chime +// AudioFilePlayer chimePlayer = new AudioFilePlayer(); +// chimePlayer.setPlayMode("asset"); +// if(msgType.equalsIgnoreCase("fire")) { +// chimePlayer.setPlayTarget("fire"); +// } else if(msgType.equalsIgnoreCase("emergency")) { +// chimePlayer.setPlayTarget("emergency"); +// } else if(msgType.equalsIgnoreCase("rescue")) { +// chimePlayer.setPlayTarget("rescue"); +// } else { +// chimePlayer.setPlayTarget("general"); +// } +// chimePlayer.start(); + + + try { + JSONObject notiObject = new JSONObject(); + notiObject.put("status", "command-receive"); + notiObject.put("ward_id", mWardId); + notiObject.put("dsr_seq", mDsrSeq); + + sendMqttMsg("ebcast/device/notify", notiObject.toString()); + } catch(Exception e) { + } + + } + } + } + }); + + mqttAndroidClient.subscribe("ebcast/client/#", 1, new IMqttMessageListener() { + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + //Log.d("MQTT", "[RECV] <" + topic + "> " + message.getPayload().length + " Bytes..."); + if(mDsrSeq != null && mDsrSeq.length() > 0) { + if (topic.startsWith("ebcast/client/cast/" + mDsrSeq)) { + byte[] buffer = message.getPayload(); + int ret = mAudioTrack.write(buffer, 0, buffer.length); + Log.d("MQTT", "[AUDIO WRITE] " + ret); + long curTime = SystemClock.uptimeMillis(); + + if(mCastReceivedNotifyDsrSeq == null || !mCastReceivedNotifyDsrSeq.equals(mDsrSeq) || curTime - mDsrCastReceivedTime > 1200) { + mCastReceivedNotifyDsrSeq = mDsrSeq; + + try { + JSONObject notiObject = new JSONObject(); + notiObject.put("status", "cast-receive"); + notiObject.put("ward_id", mWardId); + notiObject.put("dsr_seq", mDsrSeq); + + sendMqttMsg("ebcast/device/notify", notiObject.toString()); + } catch(Exception e) { + } + } + + mDsrCastReceivedTime = curTime; + } + } + } + }); + + } catch (MqttException e) { + throw new RuntimeException(e); + } + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + // 연결 실패 + Log.e("MQTT", "Connection failed: " + exception.getMessage(), exception); + try { + Thread.sleep(5000); + if(mqttAndroidClient != null) { + mqttAndroidClient.close(); + } + } catch(Exception e) { + e.printStackTrace(); + } + mqttAndroidClient = null; + + + } + }); + + + } catch (MqttException e) { + mqttAndroidClient.close(); + mqttAndroidClient = null; + e.printStackTrace(); + } + } + } + Thread.sleep(500); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }.start(); + +// new Thread() { +// @Override +// public void run() { +// while(true) { +// File delFile = new File("/mnt/obb/fire"); +// if(delFile.exists()) { +// delFile.delete(); +// +// handleNotifyScreenOrder("fire", +// "주택화재", +// "3차출동", +// "2021-10-27 01:33:24", +// "서울특별시 구로구 디지털로 1113", +// "서울특별시 구로구 디지털로 11번지", +// "보통 임신 5~6주 정도에 시작해 9~10주에 가장 심하며, 대부분 16~18주면 사라지지만 그 이상 지속되는 경우도 있다.", +// "진압차|1차|2차|3차|4차|5차"); +// } +// +// delFile = new File("/mnt/obb/rescue"); +// if(delFile.exists()) { +// delFile.delete(); +// +// handleNotifyScreenOrder("rescue", +// "주택구조", +// "1차출동", +// "2020-10-27 01:33:24", +// "김포시 고촌읍 고촌로 13", +// "김포시 고촌읍 334번지", +// "입덧은 임신 중에 느끼는 구역 및 구토 증상으로, 주로 임신 초기에 발생한다. 보통 임신 5~6주 정도에 시작해 9~10주에 가장 심하며, 대부분 16~18주면 사라지지만 그 이상 지속되는 경우도 있다.", +// "탑차|구급차|진압차|1차|2차|3차|4차|5차"); +// } +// +// try { +// sleep(100); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } +// } +// }.start(); + + + new Thread() { + @Override + public void run() { + try { + sleep(300); + } catch (InterruptedException e) { + e.printStackTrace(); + } + +// handleNotifyScreenOrder("rescue", +// "자살", +// "1차출동", +// "2024-01-04 21:18:00", +// "전라남도 보성군 우암길 4 ", +// "전라남도 보성군 회천면 율포리 315-3 ", +// "[요청기관] 전남지방경찰청 [접수일시] 2024-01-04 21:15:45\n" + +// "[재난종별] 기타경찰업무 [접수/요청자명]박대식/김원일-\n" + +// "[재난위치] (지)전라남도 보성군 회천면 율포리(회천면 ) 315-3 (경위도) 34.67045, 127.0868\n" + +// "[신고내용] 조카가 자살한다고 하고 전화를 끊었다며 / 요구조자 조카는 화순에서 거주 중 / [제3자 위치추적 정보][신고정보]1.성명 : 임승환 (남자, 삼촌)2.연락처 : 010-9887-64683.기타특이사항 : 없음[요구조자]1.성명 : 나현종 (남자, 50대)2.연락처 : 010-3753-48983.구조대상구분 : 자살기도 [위험상황]1. 상세상황 : 정보없음2. 신고자의 인지경위 : 정보없음[위치추적결과]1. 대상정보 : 2024.01.04 21:03:37 / CELL방식2. 대상주소 : 전라남도 보성군 회천면 율포리3. 대상관할 : 전남청 보성서 회천파출소 /이첩시도청 : 광주청 /이첩유형 : 공조 /이첩일자 : 2024-01-04 /이첩사건번호 : 1247 이첩사유 : 요구조자 위치값 : 전라남도 보성군 회천면 군농리(회천면 ) 1111-16 접수경로(112)", +// "회천구급/A/|회천중형/A/벌/"); + + + //handleNotifyScreenOrder("rescue", "2020-10-27 01:33:24", "김포시 고촌읍 334번지", "입덧은 임신 중에 느끼는 구역 및 구토 증상으로, 주로 임신 초기에 발생한다. 보통 임신 5~6주 정도에 시작해 9~10주에 가장 심하며, 대부분 16~18주면 사라지지만 그 이상 지속되는 경우도 있다.", "탑차|구급차|진압차|1차|2차|3차|4차|5차"); +// handleNotifyScreenOrder("rescue", +// "주택구조", +// "1차출동", +// "2020-10-27 01:33:24", +// "김포시 고촌읍 고촌로 13", +// "김포시 고촌읍 334번지", +// "입덧은 임신 중에 느끼는 구역 및 구토 증상으로, 주로 임신 초기에 발생한다. 보통 임신 5~6주 정도에 시작해 9~10주에 가장 심하며, 대부분 16~18주면 사라지지만 그 이상 지속되는 경우도 있다.", +// "탑차|구급차|진압차|1차|2차|3차|4차|5차"); +// +// try { +// sleep(5000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// +// handleNotifyScreenOrder("fire", +// "주택화재", +// "3차출동", +// "2021-10-27 01:33:24", +// "서울특별시 구로구 디지털로 1113", +// "서울특별시 구로구 디지털로 11번지", +// "보통 임신 5~6주 정도에 시작해 9~10주에 가장 심하며, 대부분 16~18주면 사라지지만 그 이상 지속되는 경우도 있다.", +// "진압차|1차|2차|3차|4차|5차"); +// +// try { +// sleep(5000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// +// handleNotifyScreenOrder("rescue", +// "주택화재", +// "2차출동", +// "2021-10-27 01:33:24", +// "김포시 고촌읍 고촌로 13", +// "김포시 고촌읍 334번지", +// "입덧은 임신 중에 느끼는 구역 및 구토 증상으로, 주로 임신 초기에 발생한다. 보통 임신 5~6주 정도에 시작해 9~10주에 가장 심하며, 대부분 16~18주면 사라지지만 그 이상 지속되는 경우도 있다.", +// "탑차|구급차|진압차|1차|2차|3차|4차|5차"); +// +// +// for(int i = 0;i < 100;i++) { +// try { +// sleep(31000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// +// handleNotifyScreenOrder("rescue", +// "주택화재", +// "2차출동" + i, +// "2021-10-27 01:33:24", +// "김포시 고촌읍 고촌로 13", +// "김포시 고촌읍 334번지", +// "입덧은 임신 중에 느끼는 구역 및 구토 증상으로, 주로 임신 초기에 발생한다. 보통 임신 5~6주 정도에 시작해 9~10주에 가장 심하며, 대부분 16~18주면 사라지지만 그 이상 지속되는 경우도 있다.", +// "탑차|구급차|진압차|1차|2차|3차|4차|5차"); +// } + } + }.start(); + + // + + /*mTickerManager.setupTicker("입덧은 임신 중에 느끼는 구역 및 구토 증상으로, 주로 임신 초기에 발생한다. 보통 임신 5~6주 정도에 시작해 9~10주에 가장 심하며, 대부분 16~18주면 사라지지만 그 이상 지속되는 경우도 있다.", + new Rect(0, 500, 1920, 600), + 0xFFFFFF, 0x000000, 2, 200); + mTickerManager.renderTicker();*/ + + /*TickerView tickerView = findViewById(R.id.tickerView); + if(tickerView != null) { + tickerView.setupTicker("테스트 텍스트 입니다.", new Rect(0, 900, 1920, 1080), 0x000000, 0xFFFFFF); + tickerView.renderTicker(); + }*/ + + /*final TextView tv = findViewById(R.id.textViewTicker); + Paint paint = new Paint(); + paint.setTextSize(tv.getTextSize()); + float spaceWidth = paint.measureText(" "); + float width = tv.getLayoutParams().width; + int dupCount = (int)(width / spaceWidth) + 1; + Log.e(TAG, String.format("spaceWidth : %2.2f, width : %2.2f, dupCount : %d", spaceWidth, width, dupCount)); + String curText = ""; + for(int i = 0;i < dupCount;i++) + curText += " "; + curText += tv.getText().toString(); + tv.setText(curText); + //tv.setMarqueeRepeatLimit(-1); + tv.setMarqueeRepeatLimit(1); + tv.setSelected(true); + new Thread() { + @Override + public void run() { + try { + sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + tv.setMarqueeSpeed(400f); + + while(true) { + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if(tv.isMarqueeStopped()) { + Log.e(TAG, "MARQUEE IS COMPLETE!!!!"); + tv.setAlpha(0); + break; + } + } + } + }.start();*/ + + new Thread() { + @Override + public void run() { + try { + sleep(300); + } catch (InterruptedException e) { + e.printStackTrace(); + } + //SystemUtil.playWaveFile("/mnt/obb/loststar.wav", 1); + +// { +// mIsRitoPlayerOver = false; +// mPlayer.openUrl(0, "/system/media/upchimes.wav"); +// while (!mIsRitoPlayerOver) { +// try { +// sleep(10); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } +// +// int mProgress = 0; +// while (mProgress < 3) +// { +// int curProgress = mProgress; +// mIsRitoPlayerOver = false; +// mPlayer.openUrl(0, "/mnt/obb/tts.wav"); +// while (!mIsRitoPlayerOver) { +// try { +// sleep(10); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } +// mProgress++; +// } +// +// mIsRitoPlayerOver = false; +// mPlayer.openUrl(0, "/system/media/downchimes.wav"); +// } + } + }.start(); + } + + /*protected void setMarqueeSpeed(TextView tv, float speed, + boolean speedIsMultiplier) { + try { + Field f; + if (tv instanceof AppCompatTextView) { + f = tv.getClass().getSuperclass().getDeclaredField("mMarquee"); + } else { + f = tv.getClass().getDeclaredField("mMarquee"); + } + f.setAccessible(true); + Object marquee = f.get(tv); + Log.e(TAG, "MARQUEE COME 0"); + if (marquee != null) { + String scrollSpeedFieldName = "mScrollUnit"; + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + scrollSpeedFieldName = "mPixelsPerSecond"; + + Field mf = marquee.getClass().getDeclaredField(scrollSpeedFieldName); + mf.setAccessible(true); + + float newSpeed = speed; + if (speedIsMultiplier) + newSpeed = mf.getFloat(marquee) * speed; + + mf.setFloat(marquee, newSpeed); + Log.e(TAG, "MARQUEE COME 1"); + } + Log.e(TAG, "MARQUEE COME 2"); + } catch (Exception e) { + e.printStackTrace(); + } + }*/ + + public void runTestStream() { + Media m = new Media(mVLC, Uri.parse("rtsp://gookmin.iptime.org:17100/Camera19")); + mVlcPlayer.stop(); + mVlcPlayer.setMedia(m); + mVlcPlayer.play(); + } + + private void createPlayer(String media) { + releasePlayer(); + try { +// if (media.length() > 0) { +// Toast toast = Toast.makeText(this, media, Toast.LENGTH_LONG); +// toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, +// 0); +// toast.show(); +// } + + // Create LibVLC + // TODO: make this more robust, and sync with audio demo + ArrayList options = new ArrayList(); + //options.add("--subsdec-encoding "); + options.add("--aout=opensles"); + //options.add("--aout=android_audiotrack"); + options.add("--audio-time-stretch"); // time stretching + //options.add("--no-audio-time-stretch"); + options.add("-vvv"); // verbosity + options.add("--http-reconnect"); + options.add("--rtsp-tcp"); + if(mIsDeviceOSIC10) { + options.add("--audio-resampler"); + //options.add("disable"); + options.add("soxr"); + //options.add("samplerate"); + //options.add("--network-caching=" + 20000); + + +// options.add("--avcodec-skip-frame"); +// options.add("2000000"); + } else { + options.add("--network-caching=" + 100); + } + mVLC = new LibVLC(this, options); + holder.setKeepScreenOn(true); + + // Create media player + mVlcPlayer = new org.videolan.libvlc.MediaPlayer(mVLC); + mVlcPlayer.setEventListener(mPlayerListener); + //mVlcPlayer.getMedia().setHWDecoderEnabled(true, true); + setMainPlayerVolume(0); + + // Set up video output + final IVLCVout vout = mVlcPlayer.getVLCVout(); + //vout.setVideoView(mSurface); + vout.setVideoView(mTexturePlayView); + //vout.setSubtitlesView(mSurfaceSubtitles); + vout.setWindowSize(1920, 1080); + vout.addCallback(this); + vout.attachViews(); + + //mMediaPlayer.setScale(1920f / 800f); + //mMediaPlayer.setAspectRatio(null); + mVlcPlayer.setAspectRatio("16:9"); + mVlcPlayer.setScale(0); + + AudioFilePlayer mutePlayer = new AudioFilePlayer(); + mutePlayer.setPlayMode("asset"); + mutePlayer.playAssetAudio("mute.wav"); + mPlayer.openUrl(0, "/system/media/mute.wav"); + +// new Thread() { +// @Override +// public void run() { +// try { +// sleep(8000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// +// try { +// Media muteMedia = new Media(mVLC, getAssets().openFd("upchimes.wav")); +// mVlcPlayer.setMedia(muteMedia); +// mVlcPlayer.play(); +// } catch(Exception e) { +// e.printStackTrace(); +// } +// } +// }.start(); + + new Thread() { + @Override + public void run() { +// AudioFilePlayer mutePlayer = new AudioFilePlayer(); +// mutePlayer.setPlayMode("asset"); +// mutePlayer.playAssetAudio("mute.wav"); +// try { +// sleep(3000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// mutePlayer.playAssetAudio("mute.wav"); + } + }.start(); + + if(media != null && media.length() > 0) { + Media m = new Media(mVLC, Uri.parse(media)); + mLastPlayUrl = media; + if(mIsDeviceOSIC10) { + m.addOption(":network-caching=" + 3000); + } + //m.addOption(":network-caching=" + 10000); + mVlcPlayer.setMedia(m); + mVLCMedia = m; + mVlcPlayer.play(); + } + } catch (Exception e) { + e.printStackTrace(); + //Toast.makeText(this, "Error creating player!", Toast.LENGTH_LONG).show(); + } + } + + private void releasePlayer() { + if (mVLC == null) + return; + mVlcPlayer.stop(); + final IVLCVout vout = mVlcPlayer.getVLCVout(); + vout.removeCallback(this); + vout.detachViews(); + //holder = null; + mVlcPlayer.release(); + mVlcPlayer = null; + mVLC.release(); + mVLC = null; + } + + public class DacScheduler extends Thread { + private String STREAM_IO_FILE = "/mnt/obb/_audio_streaming"; + private boolean mIsAlive = true; + private RandomAccessFile mPipe; + private byte[] buffer = new byte[4096]; + + public void terminate() { + mIsAlive = false; + } + + @Override + public void run() { + Log.w(TAG, "Start DAC Scheduler......."); + File streamFile = new File(STREAM_IO_FILE); + if(streamFile.exists()) { + streamFile.delete(); + } + mIsAlive = true; + byte[] streamBuffer = new byte[8192 * 30]; + byte[] muteBuffer = new byte[65536]; +// for(int i = 0;i < muteBuffer.length;i++) { +// if(i % 2 == 0) +// muteBuffer[i] = 127; +// } + int accSize = 0; + int noDacCount = 0; + boolean streamActive = false; + //SystemUtil.setupAudioOut(1); + while(mIsAlive) { + if(streamFile.exists() && !mIsOnTTSPlaying) { + if(mPipe == null) { + try { + mPipe = new RandomAccessFile(STREAM_IO_FILE, "r"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + if(mPipe != null) { + try { + int size = mPipe.read(buffer); + //Log.w(TAG, "PIPE Recv : " + size); + if(size > 0) { + byte[] dupBuffer = new byte[size * 2]; + for(int i = 0;i < size; i += 2) { + dupBuffer[i * 2] = buffer[i]; + dupBuffer[i * 2 + 1] = buffer[i + 1]; + dupBuffer[(i + 1) * 2] = buffer[i]; + dupBuffer[(i + 1) * 2 + 1] = buffer[i + 1]; + } +// System.arraycopy(dupBuffer, 0, streamBuffer, accSize, size * 2); +// accSize += size * 2; + + //SystemUtil.writeAudioOut(buffer, size); + if(!streamActive) { + streamActive = true; + SystemUtil.writeAudioOut(muteBuffer, muteBuffer.length); + } + + SystemUtil.adjustDataVolume(dupBuffer, size * 2, mLastDacVolume); + SystemUtil.writeAudioOut(dupBuffer, size * 2); + + //Log.d(TAG, "AudioStreaming Write : " + size * 2); +// if(accSize >= 8192) { +// if(!streamActive) { +// streamActive = true; +// SystemUtil.writeAudioOut(muteBuffer, muteBuffer.length); +// } +// SystemUtil.writeAudioOut(streamBuffer, accSize); +// Log.d(TAG, "AudioStreaming Write : " + accSize); +// accSize = 0; +// } + } else if(size <= 0) { + mPipe.close(); + mPipe = null; + } + } catch (IOException e) { + e.printStackTrace(); + mPipe = null; + } catch (Exception e) { + e.printStackTrace(); + mPipe = null; + } + } + + noDacCount = 0; + } else { + //Log.w(TAG, "NO Audio Streaming File..."); + streamActive = false; + if(mPipe != null) { + try { + mPipe.close(); + } catch (IOException e) { + e.printStackTrace(); + } + mPipe = null; + } + + if(!mIsOnTTSPlaying) { + noDacCount++; + if(noDacCount > 10) { + noDacCount = 0; +// SystemUtil.writeAudioOut(muteBuffer, muteBuffer.length); +// Log.w(TAG, "Write DAC Mute Sound..."); + } + } + + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + + try { + sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + public void setChannelOsdVisibility(final boolean show, final String title) { + if(show) { + runOnUiThread(new Runnable() { + @Override + public void run() { + mChannelOsdImageView.setVisibility(View.VISIBLE); + + mChannelOsdView.setText(title); + mChannelOsdView.setVisibility(View.VISIBLE); + mProgressBar.setVisibility(View.VISIBLE); + } + }); + } else { + runOnUiThread(new Runnable() { + @Override + public void run() { + mChannelOsdImageView.setVisibility(View.INVISIBLE); + mChannelOsdView.setVisibility(View.INVISIBLE); + mProgressBar.setVisibility(View.INVISIBLE); + } + }); + } + } + + public void setAlertVisibility(final boolean show, final String msg) { + if(show) { + runOnUiThread(new Runnable() { + @Override + public void run() { + TextView alertView = findViewById(R.id.textViewAlert); + alertView.setText(msg); + + FrameLayout layout = findViewById(R.id.layoutAlert); + layout.setVisibility(View.VISIBLE); + } + }); + } else { + runOnUiThread(new Runnable() { + @Override + public void run() { + FrameLayout layout = findViewById(R.id.layoutAlert); + layout.setVisibility(View.INVISIBLE); + } + }); + } + } + + public void setCarcastTitleVisibility(final boolean show, final String title) { + if(show) { + runOnUiThread(new Runnable() { + @Override + public void run() { + mCarCastTitleView.setText(title); + mCarCastTitleView.setVisibility(View.VISIBLE); + } + }); + } else { + runOnUiThread(new Runnable() { + @Override + public void run() { + mCarCastTitleView.setVisibility(View.INVISIBLE); + } + }); + } + } + + public class JsonScheduler extends Thread { + Object jsonLock = new Object(); + boolean mIsAlive = false; + int mPlayIndex = 0; + int mLastPlayErrorIndex = -1; + int mRetryCount = 0; + int mPlayerState = org.videolan.libvlc.MediaPlayer.Event.EndReached; + int mVideoDecodedCount; + int mVideoStallCount; + int mAudioDecodedCount; + int mNoDataStreamCount = 0; + + public void terminate() { + mIsAlive = false; + } + + public void updateJsonResult(JsonResult result) { + setCarcastTitleVisibility(false, null); + + synchronized (jsonLock) { + mJsonResult = result; + mPlayIndex = 0; + mRetryCount = 0; + + try { + if(result != null && result.mType != null) { + SystemUtil.TraceLog("mJsonResult is changed to : " + result.mType); + } + } catch(Exception e) { + e.printStackTrace(); + } + } + } + + public int getRetryCount() { + int count; + synchronized (jsonLock) { + count = mRetryCount; + } + return count; + } + + public void setRetryCount(int count) { + synchronized (jsonLock) { + mRetryCount = count; + } + } + + public void updateJsonTargetResult(String target, JsonResult result) { + synchronized (jsonLock) { + if(target.equalsIgnoreCase("ip")) { + mJsonIpSchedule = result; + } else if(target.equalsIgnoreCase("file")) { + mJsonFileSchedule = result; + } + } + } + + public void prepareJsonSchedule() { + if(mJsonResult != null) { + setTTSViewVisibility(false); + setWaitViewVisibility(false); + } + + synchronized (jsonLock) { + mPlayIndex = 0; + + if(mJsonResult.mType.equalsIgnoreCase(MANAGER_JSON_ORDER_TYPE_PLAY_IPCAST)) { + mPlayIndex = mJsonResult.mIpSelectedIndex; + Log.w(TAG, "PlayIndex is fixed to : " + mPlayIndex); + } + } + + if(mVlcPlayer != null) { + mVlcPlayer.stop(); + } + } + + public void advancePlayNext(int direction) { + if(mIsOnNotifyMsg || mIsOnTTSPlaying) + return; + + synchronized (jsonLock) { + mNoDataStreamCount = 0; + mRetryCount = 0; + if(mJsonResult != null) { + setCarcastTitleVisibility(false, null); + setWaitViewVisibility(false); + if(mJsonResult.mType.equalsIgnoreCase(MANAGER_JSON_ORDER_TYPE_PLAY_IPCAST)) { + mPlayIndex += direction; + + // 파일 스케쥴이 있는 경우 IP방송에서 파일스케쥴로 변경 + if(mPlayIndex < 0 || mPlayIndex >= mJsonResult.mIpCount) { + if(mJsonFileSchedule != null) { + mJsonResult = mJsonFileSchedule; + mPlayIndex = 0; + setMainPlayerVolume(0); + mVlcPlayer.stop(); + return; + } + } + + if(mPlayIndex < 0) { + mPlayIndex = mJsonResult.mIpCount - 1; + } + + mPlayIndex %= mJsonResult.mIpCount; + +// runOnUiThread(new Runnable() { +// @Override +// public void run() { +// mChannelOsdImageView.setVisibility(View.VISIBLE); +// +// //mImageViewBlack.setVisibility(View.VISIBLE); +// mChannelOsdView.setText(mJsonResult.mIpTitleList[mPlayIndex]); +// mChannelOsdView.setVisibility(View.VISIBLE); +// mProgressBar.setVisibility(View.VISIBLE); +// } +// }); + + setChannelOsdVisibility(true, mJsonResult.mIpTitleList[mPlayIndex]); + + new Thread() { + @Override + public void run() { + setMainPlayerVolume(0); + mVlcPlayer.stop(); + } + }.start(); + //mVlcPlayer.stop(); + } else { + // 다른 스케쥴에서 IP방송스케쥴로 변경 + if(mJsonIpSchedule != null) { + mJsonResult = mJsonIpSchedule; + mPlayIndex = 0; + if(direction < 0) + mPlayIndex = mJsonResult.mIpCount - 1; + advancePlayNext(0); + return; + } + } + } else { + if(mJsonIpSchedule != null) { + mJsonResult = mJsonIpSchedule; + mPlayIndex = 0; + if(direction < 0) + mPlayIndex = mJsonResult.mIpCount - 1; + + if(mLastPlayErrorIndex >= 0) { + mPlayIndex = mLastPlayErrorIndex; + mLastPlayErrorIndex = -1; + mPlayIndex += direction; + if(mPlayIndex < 0) { + mPlayIndex = mJsonResult.mIpCount - 1; + } + + mPlayIndex %= mJsonResult.mIpCount; + } + + advancePlayNext(0); + return; + } else if(mJsonFileSchedule != null) { + mJsonResult = mJsonFileSchedule; + mPlayIndex = 0; + setMainPlayerVolume(0); + mVlcPlayer.stop(); + return; + } + } + } + } + + private boolean isPlayReady() { + if(mPlayerState == org.videolan.libvlc.MediaPlayer.Event.EndReached || + mPlayerState == org.videolan.libvlc.MediaPlayer.Event.EncounteredError || + mPlayerState == org.videolan.libvlc.MediaPlayer.Event.Stopped) + return true; + + return false; + } + + private void nextPlayWithUrl(String url) { + Media m = new Media(mVLC, Uri.parse(url)); + mVlcPlayer.stop(); + mLastPlayErrorIndex = -1; + mNoDataStreamCount = 0; + mLastPlayUrl = url; + mVlcPlayer.setMedia(m); + mVLCMedia = m; + mVlcPlayer.play(); + if(mIsDeviceOSIC10) { + setMainPlayerVolume(0); + } else { + setMainPlayerVolume(mVlcMaxVolume); + } + } + + @Override + public void run() { + int reportCount = 0; + mIsAlive = true; + while(mIsAlive) { + if(SystemUtil.property_get("sys.rito.die").equals("1")) { + String hoho = null; + Log.w("LEN", "hoho : " + hoho.length()); + } + + if(mVlcPlayer != null) { + if (!mVlcPlayer.isPlaying() && isPlayReady()) { + mNoDataStreamCount = 0; + synchronized (jsonLock) { + if(mJsonResult != null) { + if(mJsonResult.mType.equalsIgnoreCase(MANAGER_JSON_ORDER_TYPE_PLAY_CONTENTS)) { + int playIndex; + + playIndex = mPlayIndex; + mPlayIndex++; + mPlayIndex %= mJsonResult.mFileCount; + + Log.w(TAG, "Play FilePath : " + mSDSavePath + "/" + mJsonResult.mFilePathList[playIndex]); + nextPlayWithUrl("file://" + mSDSavePath + "/" + mJsonResult.mFilePathList[playIndex]); + } else if(mJsonResult.mType.equalsIgnoreCase(MANAGER_JSON_ORDER_TYPE_PLAY_CARCAST)) { + mPlayIndex = 0; + mRetryCount++; + + Log.w(TAG, "Play CarUrl : " + mJsonResult.mCarAddress); + if(mJsonResult.mCarStation != null && mJsonResult.mCarCar != null) { + String carTitle = " " + mJsonResult.mCarStation + "-" + mJsonResult.mCarCar; + setCarcastTitleVisibility(true, carTitle); + } + nextPlayWithUrl(mJsonResult.mCarAddress); + } else if(mJsonResult.mType.equalsIgnoreCase(MANAGER_JSON_ORDER_TYPE_PLAY_IPCAST)) { + mRetryCount++; + + Log.w(TAG, "Play IpUrl : " + mJsonResult.mIpAddressList[mPlayIndex]); + nextPlayWithUrl(mJsonResult.mIpAddressList[mPlayIndex]); + } + } else { + SystemUtil.TraceLog("mJsonResult is NULL..."); + } + } + } else { + SystemUtil.TraceLog("mVlcPlayer.isPlaying() : " + mVlcPlayer.isPlaying() + ", isPlayReady() :" + isPlayReady()); + //IMedia media = mVlcPlayer.getMedia(); + try { + if (mVLCMedia != null) { + IMedia.Stats stats = mVLCMedia.getStats(); + if (stats != null) { + String info = String.format("[Media] VideoDecoded : %d, AudioDecoded : %d, InputBitrate : %1.4f mbps, demuxBitrate : %1.4f mbps", + stats.decodedVideo, + stats.decodedAudio, + stats.inputBitrate, + stats.demuxBitrate); + SystemUtil.TraceLog(SystemUtil.TRACE_LOG_LEVEL_2, info); + reportCount++; + if(reportCount >= 50) { + reportCount = 0; + Log.i(TAG, String.format("[Media] VideoLost : %d, AudioLost : %d", stats.lostPictures, stats.lostAbuffers)); + } + + if(!mIsDeviceOSIC10) { // 소방방송 모드 + if(stats.inputBitrate > 0) { + mNoDataStreamCount = 0; + synchronized (jsonLock) { + if (mJsonResult != null) { + if (mJsonResult.mType.equalsIgnoreCase(MANAGER_JSON_ORDER_TYPE_PLAY_IPCAST)) { + if (mVideoDecodedCount == stats.decodedVideo) { + mVideoStallCount++; + if (mVideoStallCount > 20) { + mVideoStallCount = 0; + Log.w(TAG, "Catch Video Stalled on Live!! Try Replay [" + mJsonResult.mIpAddressList[mPlayIndex] + "]"); + nextPlayWithUrl(mJsonResult.mIpAddressList[mPlayIndex]); + } + } else { + mVideoStallCount = 0; + mVideoDecodedCount = stats.decodedVideo; + } + } + } + } + } else { + if (mJsonResult != null) { + if (mJsonResult.mType.equalsIgnoreCase(MANAGER_JSON_ORDER_TYPE_PLAY_IPCAST) || + mJsonResult.mType.equalsIgnoreCase(MANAGER_JSON_ORDER_TYPE_PLAY_CARCAST)) { + mNoDataStreamCount++; + if (mNoDataStreamCount > 20) { + mNoDataStreamCount = 0; + mVlcPlayer.stop(); + } + } + } + } + } else { // added by ritoseo - 2024-09-13 + if(stats.demuxBitrate > 0) { + mNoDataStreamCount = 0; + if (mVideoDecodedCount == stats.decodedVideo) { + mVideoStallCount++; + if (mVideoStallCount > 20) { + mVideoStallCount = 0; + Log.w(TAG, "Catch Video Stalled on Live!! Player STOP!!!"); + mVlcPlayer.stop(); + } + } else { + mVideoStallCount = 0; + mVideoDecodedCount = stats.decodedVideo; + } + } else { + mNoDataStreamCount++; + if (mNoDataStreamCount > 20) { + Log.w(TAG, "Catch Stream is Stop on Live!! Player STOP!!!"); + mNoDataStreamCount = 0; + mVlcPlayer.stop(); + } + } + } + + } + } + } catch(Exception e) { + e.printStackTrace(); + } + } + } + + try { + sleep(200); + } catch (InterruptedException e) { + } + } + } + } + + public void handlePlayEvent() { + if(!mIsDeviceOSIC10) { // 소방방송 모드인 경우 + try { + if (mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.EndReached || + mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.Stopped || + mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.EncounteredError) { + + if (mJsonResult != null && mJsonResult.mType != null) { + if (mJsonResult.mType.equalsIgnoreCase(MANAGER_JSON_ORDER_TYPE_PLAY_CARCAST) || + mJsonResult.mType.equalsIgnoreCase(MANAGER_JSON_ORDER_TYPE_PLAY_IPCAST)) { + int retryCount = mJsonScheduler.getRetryCount(); + if (retryCount >= 2) { + Log.w(TAG, "스트림이 끊겨서 대기화면으로 전환합니다"); + //setState("standby"); + mJsonScheduler.mLastPlayErrorIndex = mJsonScheduler.mPlayIndex; + setWaitViewVisibility(true); + mJsonScheduler.updateJsonResult(null); + + try { + AssetFileDescriptor afd = getAssets().openFd("blackground.png"); + mVlcPlayer.play(afd); + } catch(Exception e) { + } + } + } + } + } + + if (mIsOnNotifyMsg || mIsOnTTSPlaying) + return; + + } catch(Exception e) { + e.printStackTrace(); + } + } else { // OSIC10 모드인 경우 + if (mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.EndReached || + mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.Stopped || + mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.EncounteredError) { + if(mLastPlayUrl != null) { + int retryCount = mJsonScheduler.getRetryCount(); + Log.w(TAG, "retryCount : " + retryCount); + if (retryCount == 2) { + if(!SystemUtil.APP_MODE.equals("active")) { // Browser Mode 아닌경우 + setWaitViewVisibility(true); + setState("standby"); + mVlcPlayer.stop(); + mLastPlayUrl = null; + mJsonScheduler.setRetryCount(retryCount + 1); + } + } else if (retryCount < 2) { + if(!SystemUtil.APP_MODE.equals("active")) { // Browser Mode 아닌 경우 + mJsonScheduler.setRetryCount(retryCount + 1); + } + //mVlcPlayer.stop(); + if (mShouldStopPlay) { + mLastPlayUrl = null; + mShouldPlayUrl = null; + mShouldStopPlay = false; + } + + if (mLastPlayUrl != null) { + Media m = new Media(mVLC, Uri.parse(mLastPlayUrl)); + if(mIsDeviceOSIC10) { + setupVlcMediaOption(m); + } + mVlcPlayer.setMedia(m); + mVLCMedia = m; + mVlcPlayer.play(); + } + } + } else { + Log.w(TAG, "mLastPlayUrl is null!!!"); + } + } + } + } + + + /************* + * Events + *************/ + + private org.videolan.libvlc.MediaPlayer.EventListener mPlayerListener = new MyPlayerListener(this); + private static int mLastALossCount = 0; + private static int mLastVLossCount = 0; + private static long mLastALossCheckTime = 0; + private static long mBufferingStartTime = 0; + private static long mLastPositionChangedTime = 0; + + private static class PacketLossCheck { + public long mLossTime; + public int mLossCount; + + public PacketLossCheck(long time, int count) { + mLossCount = count; + mLossTime = time; + } + } + private static LinkedBlockingQueue mLossCheck = new LinkedBlockingQueue<>(10); + + private static class MyPlayerListener implements org.videolan.libvlc.MediaPlayer.EventListener { + private WeakReference mOwner; + private PlayerChecker mChecker; + + private class PlayerChecker extends Thread { + boolean mIsAlive = true; + @Override + public void run() { + while(mIsAlive) { + long bufferingStartTime = mBufferingStartTime; + if(bufferingStartTime > 0) { + long curTime = SystemClock.uptimeMillis(); + + if(curTime - bufferingStartTime > 10 * 1000) { // 10 Seconds... + Log.w("RITO", "curTime - bufferingStartTime = " + (curTime - bufferingStartTime)); + MainActivity player = mOwner.get(); + mBufferingStartTime = curTime; + MainActivity.INSTANCE.setAlertVisibility(true, "입력 신호가 매우 약합니다."); + try { + sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + MainActivity.INSTANCE.setAlertVisibility(false, ""); +// player.mVlcPlayer.stop(); +// if(MainActivity.INSTANCE.mChannelOsdImageView.getVisibility() == View.VISIBLE) { +// MainActivity.INSTANCE.setChannelOsdVisibility(false, ""); +// } + + } + } + + /* Added by ritoseo - 2024-09-11 */ +// MainActivity player = mOwner.get(); +// String currentPlayUrl = player.mCurrentPlayUrl; +// if(currentPlayUrl != null) { +// if(currentPlayUrl.startsWith("udp://") || currentPlayUrl.startsWith("rtsp://")) { +// long curTime = SystemClock.uptimeMillis(); +// if(mLastPositionChangedTime > 0 && curTime - mLastPositionChangedTime >= 4000) { +// Log.w("RITO", "####### Detect Playback Stalled!!! ######"); +// player.mVlcPlayer.stop(); +// } +// } +// } + /* ------------------------------ */ + try { + sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public void quit() { + mIsAlive = false; + } + } + + public MyPlayerListener(MainActivity owner) { + mOwner = new WeakReference(owner); + mChecker = new PlayerChecker(); + mChecker.start(); + } + + @Override + public void onEvent(org.videolan.libvlc.MediaPlayer.Event event) { + MainActivity player = mOwner.get(); + //Log.d(TAG, "Player EVENT"); + switch(event.type) { + case org.videolan.libvlc.MediaPlayer.Event.EndReached: + player.mJsonScheduler.mPlayerState = org.videolan.libvlc.MediaPlayer.Event.EndReached; + player.mCurrentPlayUrl = null; + mBufferingStartTime = 0; + Log.d(TAG, "MediaPlayerEndReached"); + //player.releasePlayer(); + break; + case org.videolan.libvlc.MediaPlayer.Event.EncounteredError: + player.mJsonScheduler.mPlayerState = org.videolan.libvlc.MediaPlayer.Event.EncounteredError; + player.mCurrentPlayUrl = null; + mBufferingStartTime = 0; + Log.d(TAG, "Media Player Error, re-try"); + //player.releasePlayer(); + break; + case org.videolan.libvlc.MediaPlayer.Event.Playing: + player.mJsonScheduler.mPlayerState = org.videolan.libvlc.MediaPlayer.Event.Playing; + if(MainActivity.INSTANCE.mIsOnNotifyMsg || (MainActivity.INSTANCE.mIsOnTTSPlaying && !MainActivity.INSTANCE.mIsDeviceOSIC10) || + MainActivity.INSTANCE.mIsDeviceOSIC10) { + MainActivity.INSTANCE.setMainPlayerVolume(0); + } + mLastALossCheckTime = SystemClock.uptimeMillis(); + mLastALossCount = 0; + mLastVLossCount = 0; + Log.i(TAG, "RITO Playing..."); + break; + case org.videolan.libvlc.MediaPlayer.Event.Buffering: + player.mJsonScheduler.mPlayerState = org.videolan.libvlc.MediaPlayer.Event.Buffering; + + mBufferingStartTime = SystemClock.uptimeMillis(); + if(MainActivity.INSTANCE.mIsDeviceOSIC10){ + if (MainActivity.INSTANCE.mShouldPlayUrl != null) { + Log.i(TAG, "RITO Buffering... [" + player.mVlcPlayer.getMedia().getUri().toString() + "] <" + MainActivity.INSTANCE.mShouldPlayUrl + ">"); + if(!MainActivity.INSTANCE.mShouldPlayUrl.equals(player.mVlcPlayer.getMedia().getUri().toString())) { + Log.w(TAG, "FORCE Change URL To Real URL [" + MainActivity.INSTANCE.mShouldPlayUrl + "]"); + player.mVlcPlayer.play(MainActivity.INSTANCE.mShouldPlayUrl); + } + } else { + Log.i(TAG, "RITO Buffering... [" + player.mVlcPlayer.getMedia().getUri().toString() + "]"); + } + } else { + Log.i(TAG, "RITO Buffering..."); + } + + break; + case org.videolan.libvlc.MediaPlayer.Event.Paused: + Log.i(TAG, "RITO Paused..."); + player.mJsonScheduler.mPlayerState = org.videolan.libvlc.MediaPlayer.Event.Paused; + break; + case org.videolan.libvlc.MediaPlayer.Event.Stopped: + player.mJsonScheduler.mPlayerState = org.videolan.libvlc.MediaPlayer.Event.Stopped; + player.mCurrentPlayUrl = null; + Log.i(TAG, "RITO Play Stopped..."); + player.handlePlayEvent(); + mBufferingStartTime = 0; + mLastVLossCount = 0; + //player.runTestStream(); + break; + case org.videolan.libvlc.MediaPlayer.Event.TimeChanged: + if(MainActivity.INSTANCE.mChannelOsdImageView.getVisibility() == View.VISIBLE) { +// MainActivity.INSTANCE.mChannelOsdImageView.setVisibility(View.INVISIBLE); +// MainActivity.INSTANCE.mChannelOsdView.setVisibility(View.INVISIBLE); +// MainActivity.INSTANCE.mProgressBar.setVisibility(View.INVISIBLE); + //MainActivity.INSTANCE.mImageViewBlack.setVisibility(View.INVISIBLE); + MainActivity.INSTANCE.setChannelOsdVisibility(false, ""); + } + + //Log.i(TAG, "RITO TimeChanged... : " + player.mVlcPlayer.getTime()); + + if(MainActivity.INSTANCE.mIsDeviceOSIC10) { + if (MainActivity.INSTANCE.mVlcVolume == 0) { + Log.i(TAG, "RITO Volume Changer : " + player.mVlcPlayer.getTime()); + MainActivity.INSTANCE.setMainPlayerVolume(1); + new Thread() { + public void run() { + int delay = 1000; + String delayString = SystemUtil.property_get("sys.rito.volup.delay"); + if(delayString != null && delayString.length() > 0) { + delay = Integer.parseInt(delayString); + } + Log.i(TAG, "RITO Volume Delay : " + delay); + try { + sleep(delay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + for (int i = 1; i < INSTANCE.mVlcMaxVolume; i++) { + MainActivity.INSTANCE.setMainPlayerVolume(i); + try { + sleep(5); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }.start(); + } + } + + break; + case org.videolan.libvlc.MediaPlayer.Event.PositionChanged: + mBufferingStartTime = 0; + mLastPositionChangedTime = SystemClock.uptimeMillis(); + player.mJsonScheduler.setRetryCount(0); + IMedia.Stats stats = player.mVlcPlayer.getMedia().getStats(); + if(stats != null) { + SystemUtil.TraceLog(String.format("[Media Stat] VLoss : %d, ALoss : %d, Position : %2.1f", stats.lostPictures, stats.lostAbuffers, player.mVlcPlayer.getPosition())); + if(mLastVLossCount == 0 && stats.lostPictures > 0) + mLastVLossCount = stats.lostPictures; + + if(stats.lostPictures > mLastVLossCount && mLastVLossCount > 0) { + mLastVLossCount = stats.lostPictures; + if(mLossCheck.size() > 0) { + PacketLossCheck first = mLossCheck.peek(); + long timeGap = SystemClock.uptimeMillis() - first.mLossTime; + if(timeGap <= 5000) { + if(mLastVLossCount - first.mLossCount >= 3) { + new Thread() { + @Override + public void run() { + MainActivity.INSTANCE.setAlertVisibility(true, "입력 신호가 약합니다."); + try { + sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + MainActivity.INSTANCE.setAlertVisibility(false, ""); + } + }.start(); + } + + if(mLossCheck.size() >= 8) { + mLossCheck.poll(); + } + } else { + mLossCheck.poll(); + } + } + try { + mLossCheck.put(new PacketLossCheck(SystemClock.uptimeMillis(), mLastVLossCount)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if(stats.lostAbuffers >= 10) { + String strUri = player.mVlcPlayer.getMedia().getUri().toString(); + if(strUri.startsWith("udp://") || strUri.startsWith("rtsp://")) { + player.mVlcPlayer.play(player.mVlcPlayer.getMedia()); + } + } + } + //Log.i(TAG, "RITO Position : " + player.mVlcPlayer.getPosition()); + //Log.i(TAG, "RITO URI : " + player.mVlcPlayer.getMedia().getUri().toString()); + break; + default: + break; + } + } + } + + private class RitoWebSystemInterface { + public RitoWebSystemInterface() { + } + + @JavascriptInterface + public void canMenuAutoHide(int method) { + } + + @JavascriptInterface + public void setOneshotMode(int method) { + } + + + @JavascriptInterface + public void test() { + } + } + + private class RitoWebPlayerInterface { + class ChannelInfo { + int ch_no; + String url; + String title; + String screenOpt; + }; + + public int PLAYER_STATUS_QUIT = 0; + public int PLAYER_STATUS_PLAY = 1; + public int PLAYER_STATUS_PAUSE = 2; + public int PLAYER_STATUS_FASTFORWARD = 3; + public int PLAYER_STATUS_FASTBACKWARD = 4; + public int PLAYER_STATUS_INIT = 5; + + int mViewPortWidth; + int mViewPortHeight; + public ArrayList mChannelList; + public int mPlayChannelIndex; + AssetFileDescriptor mBlackAfd; + + + public RitoWebPlayerInterface() { + mViewPortWidth = 1280; + mViewPortHeight = 720; + mChannelList = new ArrayList<>(); + + try { + mBlackAfd = getAssets().openFd("blackground.png"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @JavascriptInterface + public void setViewPort(int width, int height) { + mViewPortWidth = width; + mViewPortHeight = height; + } + + @JavascriptInterface + public void quit() { + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + if(mVlcPlayer != null) { + //mVlcPlayer.stop(); + new Thread() { + @Override + public void run() { + try { + //AssetFileDescriptor afd = getAssets().openFd("blackground.png"); +// mVlcPlayer.pause(); +// mVlcPlayer.detachViews(); +// sleep(100); +// Canvas canvas = mTexturePlayView.lockCanvas(); +// if(canvas != null) { +// canvas.drawRGB(0, 0, 0); +// mTexturePlayView.unlockCanvasAndPost(canvas); +// } + mVlcPlayer.stop(); + sleep(100); + mVlcPlayer.play(mBlackAfd); + sleep(200); + +// final IVLCVout vout = mVlcPlayer.getVLCVout(); +// vout.setVideoView(mTexturePlayView); +// vout.setWindowSize(1920, 1080); +// vout.addCallback(MainActivity.INSTANCE); +// vout.attachViews(); + + mVlcPlayer.stop(); + } catch(Exception e) { + } + + } + }.start(); + + + } + } + } + + @JavascriptInterface + public void controlMethod(int method) { + } + + @JavascriptInterface + public void setEventTarget(Object target) { + + } + + @JavascriptInterface + public int getStatus() { + int status = PLAYER_STATUS_INIT; + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + if(mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.Playing + || mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.Buffering) { + status = PLAYER_STATUS_PLAY; + } else if(mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.EncounteredError + || mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.EndReached + || mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.Stopped) { + status = PLAYER_STATUS_QUIT; + } else if(mJsonScheduler.mPlayerState == org.videolan.libvlc.MediaPlayer.Event.Paused) { + status = PLAYER_STATUS_PAUSE; + } + } + + //Log.w(TAG, "getStatus() = " + status); + + return status; + } + + @JavascriptInterface + public void setPlayChannelList(String channelList, int startIdx) { + Log.w(TAG, "[setPlayChannelList] " + channelList + ", startIdx : " + startIdx); + mChannelList.clear(); + String[] tokens = channelList.split("[\r\n]"); + for(String token : tokens) { + //Log.w(TAG, "Token : " + token); + ChannelInfo info = new ChannelInfo(); + String[] subTokens = token.split("\\^"); + //Log.w(TAG, "SubTokenCount : " + subTokens.length); + if(subTokens[0] != null) { // Channel Number and URL + String[] babyToken = subTokens[0].split("="); + //Log.w(TAG, "babyToken[0] : " + babyToken[0]); + if(babyToken[0] != null) { // Channel No + if(babyToken[0].startsWith("ch")) { + info.ch_no = Integer.parseInt(babyToken[0].substring(2)); + } + } + if(babyToken[1] != null) { // URL + //Log.w(TAG, "babyToken[1] : " + babyToken[1]); + info.url = babyToken[1]; + if(info.url.contains("mc://")) { + info.url = info.url.replace("_", ":"); + } + info.url = info.url.replace("mc://2", "udp://@2"); + info.url = info.url.replace("mc://", "udp://"); + + info.url = info.url.replace("hsact://", "rtsp://"); + } + } + + if(subTokens.length >= 2 && subTokens[1] != null) { // Options + String[] babyToken = subTokens[1].split("\\|"); + if(babyToken[0] != null) { // Misc Options.. + + } + if(babyToken.length >= 2 && babyToken[1] != null) { // Title + info.title = babyToken[1]; + } + } + + if(info != null) { + mChannelList.add(info); + } + } + } + + @JavascriptInterface + public void runChannelList(int ch_no) { + Log.w(TAG, "runChannelList : " + ch_no); + MainActivity.INSTANCE.mShouldStopPlay = false; + if(USING_MAIN_PLAYER_LIBRARY_TYPE == PLAYER_LIBRARY_TYPE_VLC) { + for(int i = 0;i < mChannelList.size();i++) { + if(mChannelList.get(i).ch_no == ch_no) { + //final Media m = new Media(mVLC, Uri.parse(mChannelList.get(i).url)); + final int index = i; + mPlayChannelIndex = i; + new Thread() { + @Override + public void run() { + mVlcPlayer.stop(); + Log.w(TAG, "runChannelList URL : " + mChannelList.get(index).url); + Media m = new Media(mVLC, Uri.parse(mChannelList.get(index).url)); + if(mIsDeviceOSIC10) { + setupVlcMediaOption(m); + } + mVlcPlayer.setMedia(m); + mVLCMedia = m; + mLastPlayUrl = mChannelList.get(index).url; + mShouldPlayUrl = mLastPlayUrl; + mVlcPlayer.play(); + //Log.w(TAG, "VLCPlayer.play() 1"); + } + }.start(); + + return; + } + } + } + } + + @JavascriptInterface + public int getPlayChannelIndex() { + return mPlayChannelIndex; + } + + @JavascriptInterface + public void setScreenPosition(int left, int top, int width, int height) { + if(USING_PLAYER_VIEW_TYPE == PLAYER_VIEW_TYPE_TEXTURE_VIEW) { + final int x1 = left * 1920 / mViewPortWidth; + final int y1 = top * 1080 / mViewPortHeight; + final int width2 = width * 1920 / mViewPortWidth; + final int height2 = height * 1080 / mViewPortHeight; + + runOnUiThread(new Runnable() { + @Override + public void run() { + TextureView view = findViewById(R.id.texturePlayView); + view.setX(x1); + view.setY(y1); + view.setLayoutParams(new FrameLayout.LayoutParams(width2, height2)); + + final IVLCVout vout = mVlcPlayer.getVLCVout(); + vout.setWindowSize(width2, height2); + //mVlcPlayer.setAspectRatio(null); + } + }); + } + } + } + + private class RitoWebViewClient extends WebViewClient { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + // url 주소에 해당하는 웹페이지를 로딩 + view.loadUrl(url); + return true; + } + + @Override + public void onLoadResource(WebView view, String url) { + // 웹 페이지 리소스들을 로딩하면서 계속해서 호출된다. + super.onLoadResource(view, url); + } + + @Override + public void onPageFinished(WebView view, String url) { + // 페이지 로딩시 호출된다. + //Log.w("RITO", "onPageFinished : " + view.getProgress()); + if(SystemUtil.APP_MODE.equals("active")) { // Browser Mode 인 경우 + if(view.getProgress() == 100) { + ImageView imageView = (ImageView)findViewById(R.id.imageViewLoading); + if(imageView.getAlpha() == 1.0) { + new Thread() { + @Override + public void run() { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + setWebViewLoadingVisibility(false); + + } + }.start(); + //setWebViewLoadingVisibility(false); + } + } + } + super.onPageFinished(view, url); + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + // 페이지 요청이 시작될 경우 호출된다. + super.onPageStarted(view, url, favicon); + } + + @Override + public void onReceivedHttpError( + WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { + //Log.w("RITO", "onReceivedHttpError"); + //ImageView imgView = findViewById(R.id.imageViewWait); + //imgView.setVisibility(View.VISIBLE); + + new Thread() { + @Override + public void run() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + view.reload(); + } + }); + + } + }.start(); + //view.reload(); + } + + @Override + public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { +// if(error.getErrorCode() == WebViewClient.ERROR_CONNECT || error.getErrorCode() == WebViewClient.ERROR_TIMEOUT) { +// setWebViewLoadingVisibility(true); +// } + + if(SystemUtil.APP_MODE.equals("active")) { // added by ritoseo for v2.0.9 + setWebViewLoadingVisibility(true); + + new Thread() { + @Override + public void run() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + view.reload(); + } + }); + + } + }.start(); + } + + + super.onReceivedError(view, request, error); + } + + @Override + public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { + // TODO Auto-generated method stub + super.doUpdateVisitedHistory(view, url, isReload); + } + + + @Override + public void onUnhandledKeyEvent(WebView view, KeyEvent event) { + + + // TODO Auto-generated method stub + super.onUnhandledKeyEvent(view, event); + } + + @Override + public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { + if(mWardId != null && mWardId.length() > 0 && mDsrSeq != null && mDsrSeq.length() > 0) { + if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) { // 지령 확인 + handleDsrConfirm(); + } + } + + if(event.getKeyCode() == 183) // RED KEY - SETUP + return true; + + if(event.getKeyCode() == KeyEvent.KEYCODE_PROG_YELLOW || event.getKeyCode() == KeyEvent.KEYCODE_PROG_BLUE) { // HDMI - VOLUME KEY + return true; + } + + if(event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_STOP) { + Log.w(TAG, "[EVENT] 재생 종료 버튼 눌림 - 재생 종료합니다."); + MainActivity.INSTANCE.mShouldStopPlay = true; + ritoWebPlayerInterface.quit(); + } + + if(event.getKeyCode() == 166 && event.getAction() == KeyEvent.ACTION_UP) { // Channel UP + int chIndex = ritoWebPlayerInterface.getPlayChannelIndex(); + int chCount = ritoWebPlayerInterface.mChannelList.size(); + if(chCount > 0) { + chIndex++; + chIndex %= chCount; + int chno = ritoWebPlayerInterface.mChannelList.get(chIndex).ch_no; + setChannelOsdVisibility(true, ritoWebPlayerInterface.mChannelList.get(chIndex).title); + ritoWebPlayerInterface.runChannelList(chno); + } + } + + if(event.getKeyCode() == 167 && event.getAction() == KeyEvent.ACTION_UP) { // Channel Down + int chIndex = ritoWebPlayerInterface.getPlayChannelIndex(); + int chCount = ritoWebPlayerInterface.mChannelList.size(); + if(chCount > 0) { + chIndex--; + if(chIndex < 0) { + chIndex = chCount - 1; + } + int chno = ritoWebPlayerInterface.mChannelList.get(chIndex).ch_no; + setChannelOsdVisibility(true, ritoWebPlayerInterface.mChannelList.get(chIndex).title); + ritoWebPlayerInterface.runChannelList(chno); + } + } + + // 키를 오버로딩한것인데 주로 웹페이지를 뒤,앞 등으로 이동하게 한다. + // 왼쪽키를 누르게 되면 뒤로, 오른쪽 키는 앞으로 가게 한다. + if(event.getKeyCode() >= 199 && event.getKeyCode() <= 203) + return true; + + return super.shouldOverrideKeyEvent(view, event); + } + } + + + + private MediaSource buildMediaSource(String assetPath) { + try { + // URI 생성 + Uri uri = Uri.parse(assetPath); + + // AssetDataSource 초기화 + AssetDataSource.Factory assetDataSourceFactory = new DataSource.Factory() { + @Override + public DataSource createDataSource() { + return new AssetDataSource(MainActivity.this); + } + }; + + // ProgressiveMediaSource 생성 + return new ProgressiveMediaSource.Factory(assetDataSourceFactory) + .createMediaSource(MediaItem.fromUri(uri)); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + + + + + /** + * A native method that is implemented by the 'osic-lib' native library, + * which is packaged with this application. + */ + public native String stringFromJNI(); + public native void nativeInit(String version); + public native void terminateService(); + public native void doSync(); + public native void setState(String target); + public native String getIPAddress(); + public native String getNetmask(); + public native String getGateway(); + public native String getServerAddress(); + public native int getServerPort(); + public native String getConfigValue(String target); + public native String getFileCksum(String target); + + public native void sendSocketMessage(String target); +} diff --git a/app/src/main/java/kr/co/rito/osicmanager/RWebView.java b/app/src/main/java/kr/co/rito/osicmanager/RWebView.java new file mode 100644 index 0000000..a46e436 --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/RWebView.java @@ -0,0 +1,55 @@ +package kr.co.rito.osicmanager; + +import android.content.Context; +import android.graphics.AvoidXfermode; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.webkit.WebView; + +public class RWebView extends WebView { + public RWebView(Context context) { + super(context); + setLayerType(LAYER_TYPE_SOFTWARE, null); + + } + + public RWebView(Context context, AttributeSet attrs) { + super(context, attrs); + Paint p = new Paint(); + p.setARGB(255, 255, 0, 255); + int removeColor = p.getColor(); + p.setXfermode(new AvoidXfermode(removeColor, 0, AvoidXfermode.Mode.TARGET)); + setLayerType(LAYER_TYPE_SOFTWARE, p); + } + + public RWebView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + Paint p = new Paint(); + p.setARGB(255, 255, 0, 255); + int removeColor = p.getColor(); + p.setXfermode(new AvoidXfermode(removeColor, 0, AvoidXfermode.Mode.TARGET)); + setLayerType(LAYER_TYPE_SOFTWARE, p); + + } + + public RWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + Paint p = new Paint(); + p.setARGB(255, 255, 0, 255); + int removeColor = p.getColor(); + p.setXfermode(new AvoidXfermode(removeColor, 0, AvoidXfermode.Mode.TARGET)); + + setLayerType(LAYER_TYPE_SOFTWARE, p); + } + + @Override + protected void onDraw(android.graphics.Canvas canvas) { + Paint p = new Paint(); + p.setARGB(255, 255, 0, 255); + int removeColor = p.getColor(); + p.setAlpha(1); // if Alpha is 0 it doesn't work. I don't know why + p.setXfermode(new AvoidXfermode(removeColor, 0, AvoidXfermode.Mode.TARGET)); + canvas.drawPaint(p); + super.onDraw(canvas); + } +} diff --git a/app/src/main/java/kr/co/rito/osicmanager/SettingActivity.java b/app/src/main/java/kr/co/rito/osicmanager/SettingActivity.java new file mode 100644 index 0000000..927d139 --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/SettingActivity.java @@ -0,0 +1,167 @@ +package kr.co.rito.osicmanager; + +import android.app.Instrumentation; +import android.content.Intent; +import android.os.Bundle; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.tabs.TabLayout; + +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.AppCompatActivity; + +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.TextView; + +import kr.co.rito.osicmanager.ui.main.PlaceholderFragment; +import kr.co.rito.osicmanager.ui.main.SectionsPagerAdapter; + +public class SettingActivity extends AppCompatActivity implements TabLayout.OnTabSelectedListener { + + static int test = 0; + Instrumentation mInstrument; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.activity_setting); + + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + +// SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager()); +// ViewPager viewPager = findViewById(R.id.view_pager); +// viewPager.setAdapter(sectionsPagerAdapter); +// TabLayout tabs = findViewById(R.id.tabs); +// tabs.setupWithViewPager(viewPager); +// tabs.addOnTabSelectedListener(this); +// FloatingActionButton fab = findViewById(R.id.fab); +// +// mInstrument = new Instrumentation(); +// +// fab.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View view) { +// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) +// .setAction("Action", null).show(); +// } +// }); + } + + @Override + public void onStart() { + super.onStart(); + + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + + SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager()); + ViewPager viewPager = findViewById(R.id.view_pager); + viewPager.setAdapter(sectionsPagerAdapter); + TabLayout tabs = findViewById(R.id.tabs); + tabs.setupWithViewPager(viewPager); + tabs.addOnTabSelectedListener(this); + FloatingActionButton fab = findViewById(R.id.fab); + + mInstrument = new Instrumentation(); + + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + }); + + } + + @Override + public void onTabSelected(TabLayout.Tab tab) { + String tabName = tab.getText().toString(); + Log.w("RITO", "TabSelected : " + tabName); + + tab.view.requestFocus(); + + /*ViewPager viewPager = findViewById(R.id.view_pager); + viewPager.setCurrentItem(tab.getPosition());*/ + + /*TextView view = findViewById(R.id.textViewVersionValue); + view.setText("" + test); + test++;*/ + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + String tabName = tab.getText().toString(); + if(tabName.equals("네트워크 설정")) { + + } else if(tabName.equals("서버 설정")) { + } else if(tabName.equals("기기 정보")) { + + } + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + + } + + private void makeKeyEvent(final int keycode) { + new Thread(new Runnable() { + @Override + public void run() { + try { + mInstrument.sendKeyDownUpSync(keycode); + } catch(Exception e) { + e.printStackTrace(); + } + } + }).start(); + } + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if(event.getKeyCode() == 183) { // SETUP KEY +// ViewPager viewPager = findViewById(R.id.view_pager); +// SectionsPagerAdapter sectionsPagerAdapter = (SectionsPagerAdapter)viewPager.getAdapter(); +// PlaceholderFragment fragment = (PlaceholderFragment)sectionsPagerAdapter.getItem(1); +// fragment.saveSettings(); +// sectionsPagerAdapter.getItem(1).onStop(); +// Intent intent = new Intent(this, MainActivity.class); +// intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); +// startActivity(intent); + //SystemUtil.shellCommand("ritosysc SHELL-ORDER=killall kr.co.rito.osicmanager"); + SystemUtil.shellCommand("ritosysc SHELL-ORDER=am force-stop kr.co.rito.osicmanager"); + /*finish();*/ + return true; + } else if(event.getKeyCode() == 200) { // VFD LEFT + makeKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT); + return true; + } else if(event.getKeyCode() == 201) { // VFD RIGHT + makeKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT); + return true; + } else if(event.getKeyCode() == 202) { // VFD UP + makeKeyEvent(KeyEvent.KEYCODE_DPAD_UP); + return true; + } else if(event.getKeyCode() == 203) { // VFD DOWN + makeKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN); + return true; + } else if(event.getKeyCode() == 199) { // VFD OKAY + makeKeyEvent(KeyEvent.KEYCODE_ENTER); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/rito/osicmanager/SystemUtil.java b/app/src/main/java/kr/co/rito/osicmanager/SystemUtil.java new file mode 100644 index 0000000..ab5dcd8 --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/SystemUtil.java @@ -0,0 +1,369 @@ +package kr.co.rito.osicmanager; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Environment; +import android.util.Log; +import android.widget.Toast; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.StringTokenizer; + +public class SystemUtil { + public final static String TAG = "OsicUtil"; + + public static String APP_MODE = "passive"; + + public static void shellCommand(String order) { + try { + Process process = Runtime.getRuntime().exec(order); + process.waitFor(); + process.destroy(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void logStart() { + String filePath = Environment.getExternalStorageDirectory() + "/logcat.txt"; + try { + File logFile = new File(filePath); + if (logFile.exists()) { + Log.d(TAG, "logStart starTime=" + System.currentTimeMillis() + " fileLength=" + logFile.length()); + } + if (logFile.exists() && logFile.length() > 100 * 1024 * 1024) { + logFile.delete(); + } + Runtime.getRuntime().exec(new String[]{"logcat", "-c"}); + Runtime.getRuntime().exec(new String[]{"logcat", "-f", filePath}); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void shellCommand(String[] order) { + try { + Process process = Runtime.getRuntime().exec(order); + process.waitFor(); + process.destroy(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void property_set(String key, String val) throws IllegalArgumentException { + try { + Class SystemProperties = Class.forName("android.os.SystemProperties"); + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes = { String.class, String.class }; + Method set = SystemProperties.getMethod("set", paramTypes); + //Parameters + Object[] params = { key, val }; + set.invoke(SystemProperties, params); } + catch (IllegalArgumentException e) + { + e.printStackTrace(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public static String property_get(String key) { + String ret = ""; + try { + Class SystemProperties = Class.forName("android.os.SystemProperties"); + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes = { String.class }; + Method get = SystemProperties.getMethod("get", paramTypes); + //Parameters + Object[] params = { key }; + ret = (String) get.invoke(SystemProperties, params); + } + catch (IllegalArgumentException e) + { + ret = ""; + e.printStackTrace(); + Log.e(TAG, "IllegalArgumentException e: "+ e.toString()); + } + catch (Exception e) + { + ret = ""; + e.printStackTrace(); + Log.e(TAG, "Exception e: "+ e.toString()); + } + return ret; + } + + public static String getVersionInfo(Context context) { + String version = "Unknown"; + PackageInfo packageInfo; + + if (context == null) { + return version; + } + try { + packageInfo = context.getApplicationContext() + .getPackageManager() + .getPackageInfo(context.getApplicationContext().getPackageName(), 0 ); + version = packageInfo.versionName; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "getVersionInfo :" + e.getMessage()); + } + return version; + } + + public static void copyFile(File sourceFile, File destFile) { + if (!destFile.getParentFile().exists()) + destFile.getParentFile().mkdirs(); + + if (!destFile.exists()) { + try { + destFile.createNewFile(); + } catch(Exception e) { + e.printStackTrace(); + return; + } + } + + FileChannel source = null; + FileChannel destination = null; + + try { + source = new FileInputStream(sourceFile).getChannel(); + destination = new FileOutputStream(destFile).getChannel(); + destination.transferFrom(source, 0, source.size()); + } catch(Exception e) { + e.printStackTrace(); + } + + if (source != null) { + try { + source.close(); + } catch (Exception e) { + } + } + if (destination != null) { + try { + destination.close(); + } catch (Exception e) { + } + } + } + + public static final String TRACE_LOG_LEVEL_1 = "1"; + public static final String TRACE_LOG_LEVEL_2 = "2"; + public static final String TRACE_LOG_LEVEL_3 = "3"; + public static final String TRACE_LOG_LEVEL_4 = "4"; + public static final String TRACE_LOG_LEVEL_5 = "5"; + + + public static void TraceLineLog(String message) { + StackTraceElement element = Thread.currentThread().getStackTrace()[3]; // 호출한 메서드의 스택 정보 + String fullMessage = String.format("%s (%s:%d)", message, element.getFileName(), element.getLineNumber()); + Log.d(TAG, fullMessage); + } + + + public static void TraceLog(String log) { + TraceLog(TRACE_LOG_LEVEL_1, log); + } + + public static void TraceLog(String level, String log) { + String traceMode = property_get("sys.rito.trace"); + if(traceMode.equals(level)) { + Log.d("OsicTrace", log); + } + } + + public static class StorageInfo { + + public final String path; + public final boolean readonly; + public final boolean removable; + public final boolean usb; + public final boolean mmc; + public final int number; + + StorageInfo(String path, boolean readonly, boolean removable, int number, boolean usb, boolean mmc) { + this.path = path; + this.readonly = readonly; + this.removable = removable; + this.number = number; + this.usb = usb; + this.mmc = mmc; + } + + public String getDisplayName() { + StringBuilder res = new StringBuilder(); + if (!removable) { + res.append("Internal SD card"); + } else if (number > 1) { + res.append("SD card " + number); + } else { + if(usb) { + res.append("USB disk"); + } else { + res.append("SD card"); + } + } + if (readonly) { + res.append(" (Read only)"); + } + return res.toString(); + } + } + + public static String getSDCardPath() { + List list = getStorageList(); + for(StorageInfo info : list) { + if(info.mmc) + return info.path; + } + + return null; + } + + public static void adjustDataVolume(byte[] data, int length, int volume) { + if(volume < 100) { + for(int j = 0;j < length && j < data.length - 1;j += 2) { + short vols = (short)((int)((data[j + 1] << 8) & 0x00FF00) + (int)(data[j] & 0x00FF)); + double volf = vols; + volf *= (double)volume; + volf /= (double)100; + vols = (short)volf; + data[j + 1] = (byte)(((vols & 0x00FF00) >> 8) & 0x00FF); + data[j] = (byte)((vols & 0x00FF)); + } + } + } + + public static List getStorageList() { + + List list = new ArrayList(); + String def_path = Environment.getExternalStorageDirectory().getPath(); + boolean def_path_removable = Environment.isExternalStorageRemovable(); + String def_path_state = Environment.getExternalStorageState(); + boolean def_path_available = def_path_state.equals(Environment.MEDIA_MOUNTED) + || def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY); + boolean def_path_readonly = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY); + + HashSet paths = new HashSet(); + int cur_removable_number = 1; + + if (def_path_available) { + paths.add(def_path); + list.add(0, new StorageInfo(def_path, def_path_readonly, def_path_removable, def_path_removable ? cur_removable_number++ : -1, false, false)); + } + + BufferedReader buf_reader = null; + try { + buf_reader = new BufferedReader(new FileReader("/proc/mounts")); + String line; + Log.d(TAG, "/proc/mounts"); + while ((line = buf_reader.readLine()) != null) { + Log.d(TAG, line); + if (line.contains("vfat") || line.contains("/mnt")) { + StringTokenizer tokens = new StringTokenizer(line, " "); + String mount_device = tokens.nextToken(); //device + String mount_point = tokens.nextToken(); //mount point + if (paths.contains(mount_point)) { + continue; + } + String unused = tokens.nextToken(); //file system + List flags = Arrays.asList(tokens.nextToken().split(",")); //flags + boolean readonly = flags.contains("ro"); + + if (line.contains("/dev/block/vold")) { + if (!line.contains("/mnt/secure") + && !line.contains("/mnt/asec") + && !line.contains("/mnt/obb") + && !line.contains("/dev/mapper") + && !line.contains("tmpfs")) { + boolean usb = false; + boolean mmc = false; + paths.add(mount_point); + if(mount_device.contains("/dev/block/vold/public:8")) + usb = true; + if(mount_device.contains("/dev/block/vold/public:179")) + mmc = true; + + list.add(new StorageInfo(mount_point, readonly, true, cur_removable_number++, usb, mmc)); + } + } + } + } + + } catch (FileNotFoundException ex) { + ex.printStackTrace(); + } catch (IOException ex) { + ex.printStackTrace(); + } finally { + if (buf_reader != null) { + try { + buf_reader.close(); + } catch (IOException ex) {} + } + } + return list; + } + + public static boolean saveByteArrayToFile(String filePath, byte[] data, int length) { + if (filePath == null || data == null) { + return false; + } + + FileOutputStream fos = null; + try { + File file = new File(filePath); + fos = new FileOutputStream(file, true); + fos.write(data, 0, length); + fos.flush(); + //return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + return true; + } + + public native static String getIPAddress(); + public native static String getConfigValue(String target); + public native static void setConfigValue(String target, String value); + + public static int GPIO_IN = 0; + public static int GPIO_OUT = 1; + public static int GPIO_LOW = 0; + public static int GPIO_HIGH = 1; + public native static int getPinValue(int pinNo); + public native static int setPinValue(int pinNo, int value); + public native static int pinSetup(int pinNo, int pinDirection); + public native static int playWaveFile(String filePath, int device); + public native static int setupAudioOut(int device); + public native static int writeAudioOut(byte[] buffer, int size); +} diff --git a/app/src/main/java/kr/co/rito/osicmanager/TickerManager.java b/app/src/main/java/kr/co/rito/osicmanager/TickerManager.java new file mode 100644 index 0000000..7a3bb24 --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/TickerManager.java @@ -0,0 +1,192 @@ +package kr.co.rito.osicmanager; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Paint; +import android.graphics.Rect; +import android.net.ConnectivityManager; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +public class TickerManager { + private final static String TAG = "TickerManager"; + Activity mActivity; + TextView mTickerView; + String mTickerText; + Rect mPosition; + int mForeAlpha; + int mBackAlpha; + int mForeColor; + int mBackColor; + int mTickCount; + float mTickSpeed; + boolean mScriptActive; + Script mLastScript; + + class Script extends Thread { + @Override + public void run() { + Log.i(TAG, "Script Start"); + mScriptActive = true; +// mTickerView.setSelected(true); +// try { +// sleep(50); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// mTickerView.setSelected(false); +// try { +// sleep(50); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } + try { + sleep(50); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + MainActivity.INSTANCE.runOnUiThread(new Runnable() { + @Override + public void run() { + mTickerView.setSelected(true); + } + }); + try { + sleep(20); + } catch (InterruptedException e) { + e.printStackTrace(); + } + //mTickerView.setSelected(true); + while(!mTickerView.isMarqueeActive()) { + try { + sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + mTickerView.setAlpha(1); + + mTickerView.setMarqueeSpeed(mTickSpeed); + while(!mTickerView.isMarqueeStopped()) { + try { + sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +// mTickerView.setAlpha(0.01f); +// mTickerView.setSelected(false); + + MainActivity.INSTANCE.runOnUiThread(new Runnable() { + @Override + public void run() { + mTickerView.setAlpha(0.01f); + mTickerView.setSelected(false); + } + }); + + Log.i(TAG, "Script End"); + } + } + + public TickerManager(Activity activity, TextView tickerView) { + mActivity = activity; + mTickerView = tickerView; + mForeAlpha = 0xFF000000; + mBackAlpha = 0xFE000000; + } + + public void setupTicker(String text, Rect position, int foreColor, int backColor, int tickCount, float tickSpeed) { + mTickerText = text; + mPosition = position; + mForeColor = (mForeAlpha | foreColor); + mBackColor = (mBackAlpha | backColor); + mTickCount = tickCount; + mTickSpeed = tickSpeed * 50; + } + + public void renderTicker() { + Paint paint = new Paint(); + final float textSize = mPosition.height() * 0.5f; + paint.setTextSize(textSize); + float spaceWidth = paint.measureText(" "); + float width = mPosition.width(); + int dupCount = (int)(width / spaceWidth) + 1; + Log.e(TAG, String.format("[Position] Width : %d, Height : %d, spaceWidth : %2.2f, width : %2.2f, dupCount : %d", mPosition.width(), mPosition.height(), spaceWidth, width, dupCount)); + String curText = ""; + for(int i = 0;i < dupCount;i++) + curText += " "; + curText += mTickerText; + final String _sendText = new String(curText); + mScriptActive = false; + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + +// if(mLastScript != null) { +// try { +// mLastScript.join(); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// try { +// Thread.sleep(150); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } + +// mTickerView.setText(""); +// mTickerView.setSelected(true); +// mTickerView.setSelected(false); + + //mTickerView.setText(mTickerText); +// FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); +// params.setMargins(mPosition.left, mPosition.top, mPosition.right, mPosition.bottom); +// mTickerView.setLayoutParams(params); + //mTickerView.layout(mPosition.left, mPosition.top, mPosition.right, mPosition.bottom); + mTickerView.setX(mPosition.left); + mTickerView.setY(mPosition.top); + mTickerView.getLayoutParams().width = (mPosition.right - mPosition.left); + mTickerView.getLayoutParams().height = (int)((mPosition.bottom - mPosition.top) * 1.0); + mTickerView.setGravity(Gravity.CENTER); +// mTickerView.setHeight((mPosition.bottom - mPosition.top)); +// mTickerView.setWidth((mPosition.right - mPosition.left)); + mTickerView.setTextSize(textSize); + mTickerView.setText(_sendText); + mTickerView.setHorizontallyScrolling(true); + mTickerView.setSingleLine(true); + mTickerView.setTextColor(mForeColor); + mTickerView.setBackgroundColor(mBackColor); + mTickerView.setMarqueeRepeatLimit(mTickCount); + + + + //int curGravity = mTickerView.getGravity(); +// mTickerView.setGravity(Gravity.AXIS_PULL_BEFORE); +// float spacing = mTickerView.getLetterSpacing(); +// mTickerView.setLetterSpacing(spacing + 0.1f); +// mTickerView.setLetterSpacing(spacing); +// mTickerView.setGravity(Gravity.CENTER_VERTICAL); +// mTickerView.setAlpha(1); +// mTickerView.invalidate(); +// mTickerView.bringToFront(); + + + mLastScript = new Script(); + mLastScript.start(); + } + }); + + } + + +} diff --git a/app/src/main/java/kr/co/rito/osicmanager/TickerView.java b/app/src/main/java/kr/co/rito/osicmanager/TickerView.java new file mode 100644 index 0000000..f0a9ba7 --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/TickerView.java @@ -0,0 +1,128 @@ +package kr.co.rito.osicmanager; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.view.TextureView; + +public class TickerView extends TextureView { + + RenderScript mRenderer; + Rect mPosition; + String mTickerText; + int mForeColor; + int mBackColor; + + public TickerView(Context context) { + super(context); + setOpaque(false); + } + + public TickerView(Context context, AttributeSet attrs) { + super(context, attrs); + setOpaque(false); + } + + public TickerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setOpaque(false); + } + + public TickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setOpaque(false); + } + + private static Bitmap getTextImageWithSizeDetail(String _txt, String _fontName, float _text_size, int _height, + int _colorCode, int _shadowColorCode, float _shadowOffsetX, float _shadowOffsetY, int align) { + Paint textPaint = new Paint(); + textPaint.setTextSize(_text_size); + float _measureSize = textPaint.measureText(_txt); + int _width = (int)_measureSize + 1; + + Bitmap textBitmap = Bitmap.createBitmap(_width, _height, Bitmap.Config.ARGB_8888); +// textBitmap.eraseColor(0x8844ff44); + Canvas canvas = new Canvas(textBitmap); + Typeface typeface = Typeface.create(_fontName, Typeface.NORMAL); + + setAutoTextSize(_text_size, textPaint, _txt, _width); + textPaint.setAntiAlias(true); + textPaint.setColor(_colorCode); + textPaint.setShadowLayer(2f, _shadowOffsetX, _shadowOffsetY, _shadowColorCode); + textPaint.setTypeface(typeface); + Rect bounds = new Rect(); + textPaint.getTextBounds(_txt, 0, _txt.length(), bounds); + float measureTxt = textPaint.measureText(_txt); + float w = 0f, h = Math.abs(bounds.top); // 상단정렬 +// h = _height - Math.abs(bounds.bottom); //하단정렬 + if (align == -1) { + h += (_height - h - Math.abs(bounds.bottom)) / 2; + } else if (align == 0) { + w = (_width - measureTxt) / 2; + h += (_height - h - Math.abs(bounds.bottom)) / 2; + } else if (align == 1) { + w = _width - measureTxt; + h += (_height - h - Math.abs(bounds.bottom)) / 2; + } + canvas.drawText(_txt, w, h, textPaint); + return textBitmap; + } + + static void setAutoTextSize(float textSize, Paint paint, String text, int width) { + paint.setTextSize(textSize); + float _measureSize = paint.measureText(text); + if (_measureSize >= width) { + setAutoTextSize(--textSize, paint, text, width); + } + } + + class RenderScript extends Thread { + @Override + public void run() { + int pos_x = mPosition.right; + //Bitmap textBitmap = getTextImageWithSizeDetail(mTickerText, "", ); + + while(true) { + Canvas canvas = lockCanvas(); + if (canvas != null) { + Paint paint = new Paint(); + paint.setTextSize(mPosition.height()); + Paint clearPaint = new Paint(); + clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + canvas.drawPaint(clearPaint); + canvas.drawRect(mPosition, paint); + canvas.drawColor(mForeColor); + canvas.drawText(mTickerText, pos_x, mPosition.top + 40, paint); + unlockCanvasAndPost(canvas); + } + + try { + sleep(33); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + pos_x -= 5; + } + } + } + + + public void setupTicker(String text, Rect position, int foreColor, int backColor) { + mTickerText = text; + mPosition = position; + mForeColor = foreColor; + mBackColor = backColor; + } + + public void renderTicker() { + mRenderer = new RenderScript(); + //mRenderer.start(); + } +} diff --git a/app/src/main/java/kr/co/rito/osicmanager/ui/main/PageViewModel.java b/app/src/main/java/kr/co/rito/osicmanager/ui/main/PageViewModel.java new file mode 100644 index 0000000..7d9befb --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/ui/main/PageViewModel.java @@ -0,0 +1,30 @@ +package kr.co.rito.osicmanager.ui.main; + +import androidx.arch.core.util.Function; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Transformations; +import androidx.lifecycle.ViewModel; + +public class PageViewModel extends ViewModel { + + private MutableLiveData mIndex = new MutableLiveData<>(); + private LiveData mText = Transformations.map(mIndex, new Function() { + @Override + public String apply(Integer input) { + return "Hello world from section: " + input; + } + }); + + public void setIndex(int index) { + mIndex.setValue(index); + } + + public int getIndex() { + return mIndex.getValue(); + } + + public LiveData getText() { + return mText; + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/rito/osicmanager/ui/main/PlaceholderFragment.java b/app/src/main/java/kr/co/rito/osicmanager/ui/main/PlaceholderFragment.java new file mode 100644 index 0000000..40f5d21 --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/ui/main/PlaceholderFragment.java @@ -0,0 +1,318 @@ +package kr.co.rito.osicmanager.ui.main; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProviders; + +import kr.co.rito.osicmanager.MainActivity; +import kr.co.rito.osicmanager.R; +import kr.co.rito.osicmanager.SystemUtil; + +/** + * A placeholder fragment containing a simple view. + */ +public class PlaceholderFragment extends Fragment { + + private static final String ARG_SECTION_NUMBER = "section_number"; + + private PageViewModel pageViewModel; + private boolean mIpReloadAlive = false; + + public static PlaceholderFragment newInstance(int index) { + PlaceholderFragment fragment = new PlaceholderFragment(); + Bundle bundle = new Bundle(); + bundle.putInt(ARG_SECTION_NUMBER, index); + fragment.setArguments(bundle); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + getActivity().getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + + pageViewModel = ViewModelProviders.of(this).get(PageViewModel.class); + int index = 1; + if (getArguments() != null) { + index = getArguments().getInt(ARG_SECTION_NUMBER); + } + pageViewModel.setIndex(index); + } + + private void infoUpdate(final View rootView) { + Log.w("RITO", "infoUpdate()"); + TextView view = rootView.findViewById(R.id.textViewVersionValue); + //String value = SystemUtil.property_get("rito.system.version"); + String value = SystemUtil.getVersionInfo(getContext()); + if(value.length() == 0) { + value = "1"; + } + view.setText(value); + + view = rootView.findViewById(R.id.textViewInfoSerialValue); + value = SystemUtil.property_get("rito.board.serial"); + view.setText(value); + + view = rootView.findViewById(R.id.textViewInfoMacValue); + value = SystemUtil.property_get("rito.board.mac"); + view.setText(value); + + //SystemUtil _sysUtil = new SystemUtil(); + if(!mIpReloadAlive) { + mIpReloadAlive = true; + new Thread("ipreload") { + @Override + public void run() { + while(mIpReloadAlive) { + final TextView _view = rootView.findViewById(R.id.textViewInfoIpValue); + final String _value = SystemUtil.getIPAddress(); + MainActivity.INSTANCE.runOnUiThread(new Runnable() { + @Override + public void run() { + Log.w("RITO", "Draw IP : " + _value); + _view.setText(_value); + }}); + + try { + sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }.start(); + } +// view = rootView.findViewById(R.id.textViewInfoIpValue); +// value = SystemUtil.getIPAddress(); +// view.setText(value); + } + + private void netVisibility(View rootView, boolean visible) { + int visibility = View.INVISIBLE; + if(visible) { + visibility = View.VISIBLE; + } + + TextView textView = rootView.findViewById(R.id.textViewNetIp); + textView.setVisibility(visibility); + textView = rootView.findViewById(R.id.textViewNetGateway); + textView.setVisibility(visibility); + textView = rootView.findViewById(R.id.textViewNetmask); + textView.setVisibility(visibility); + textView = rootView.findViewById(R.id.textViewNetDns); + textView.setVisibility(visibility); + + EditText editText = rootView.findViewById(R.id.editTextNetIpValue); + editText.setVisibility(visibility); + editText = rootView.findViewById(R.id.editTextNetGatewayValue); + editText.setVisibility(visibility); + editText = rootView.findViewById(R.id.editTextNetmaskValue); + editText.setVisibility(visibility); + editText = rootView.findViewById(R.id.editTextNetDnsValue); + editText.setVisibility(visibility); + } + + private void netUpdate(final View rootView) { + //SystemUtil _sysUtil = new SystemUtil(); + String value = SystemUtil.getConfigValue("ipv4type"); + + RadioButton button = rootView.findViewById(R.id.radioButtonStatic); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Log.w("RITO", "STATIC CLICK"); + netVisibility(rootView, true); + } + }); + + button = rootView.findViewById(R.id.radioButtonDhcp); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Log.w("RITO", "DHCP CLICK"); + netVisibility(rootView, false); + } + }); + + if(value.equalsIgnoreCase("static")) { + RadioButton view = rootView.findViewById(R.id.radioButtonStatic); + view.performClick(); + view.requestFocus(); + + EditText editText = rootView.findViewById(R.id.editTextNetIpValue); + value = SystemUtil.getConfigValue("ipaddr"); + editText.setText(value); + editText = rootView.findViewById(R.id.editTextNetGatewayValue); + value = SystemUtil.getConfigValue("gateway"); + editText.setText(value); + editText = rootView.findViewById(R.id.editTextNetmaskValue); + value = SystemUtil.getConfigValue("netmask"); + editText.setText(value); + editText = rootView.findViewById(R.id.editTextNetDnsValue); + value = SystemUtil.getConfigValue("dns"); + editText.setText(value); + } else { + RadioButton view = rootView.findViewById(R.id.radioButtonDhcp); + view.performClick(); + view.requestFocus(); + + //netVisibility(rootView, false); + } + } + + private void serverUpdate(final View rootView) { + //SystemUtil _sysUtil = new SystemUtil(); + EditText editText = rootView.findViewById(R.id.editTextServerIpValue); + String value = SystemUtil.getConfigValue("svraddr"); + editText.setText(value); + + editText = rootView.findViewById(R.id.editTextServerPortValue); + value = SystemUtil.getConfigValue("svrport"); + if(value.length() == 0) + value = "10000"; + editText.setText(value); + } + + private void updateSvrUpdate(final View rootView) { + //SystemUtil _sysUtil = new SystemUtil(); + EditText editText = rootView.findViewById(R.id.editTextUpdateUrlValue); + String value = SystemUtil.getConfigValue("updatesvr"); + editText.setText(value); + } + + + public void saveSettings(String target) { + EditText editText; + String value; + + if(target.equals("server")) { + editText = getView().findViewById(R.id.editTextServerIpValue); + value = editText.getText().toString(); + if (value.length() > 0) { + SystemUtil.setConfigValue("svraddr", value); + } + + editText = getView().findViewById(R.id.editTextServerPortValue); + value = editText.getText().toString(); + if (value.length() > 0) { + SystemUtil.setConfigValue("svrport", value); + } + + editText = getView().findViewById(R.id.editTextUpdateUrlValue); + value = editText.getText().toString(); + if (value.length() > 0) { + SystemUtil.setConfigValue("updatesvr", value); + } + } + + if(target.equals("network")) { + RadioButton _radio = getView().findViewById(R.id.radioButtonStatic); + if (_radio.isChecked()) { + editText = getView().findViewById(R.id.editTextNetIpValue); + value = editText.getText().toString(); + if (value.length() > 0) { + SystemUtil.setConfigValue("ipaddr", value); + } + + editText = getView().findViewById(R.id.editTextNetGatewayValue); + value = editText.getText().toString(); + if (value.length() > 0) { + SystemUtil.setConfigValue("gateway", value); + } + + editText = getView().findViewById(R.id.editTextNetmaskValue); + value = editText.getText().toString(); + if (value.length() > 0) { + SystemUtil.setConfigValue("netmask", value); + } + + editText = getView().findViewById(R.id.editTextNetDnsValue); + value = editText.getText().toString(); + if (value.length() > 0) { + SystemUtil.setConfigValue("dns", value); + } + + SystemUtil.shellCommand("ritosysc NETAPPLY=STATIC"); + } else { + SystemUtil.shellCommand("ritosysc NETAPPLY=DHCP"); + } + } + } + + + @Override + public void onStop() { + mIpReloadAlive = false; + super.onStop(); + } + + @Override + public void onPause() { + //saveSettings(); + super.onPause(); + } + + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_setting, container, false); + final TextView textView = root.findViewById(R.id.section_label); + Log.w("RITO", "onCreateView : " + pageViewModel.getIndex()); + if(pageViewModel.getIndex() == 1) { + root.findViewById(R.id.layoutInfo).setVisibility(View.VISIBLE); + root.findViewById(R.id.layoutNetwork).setVisibility(View.INVISIBLE); + root.findViewById(R.id.layoutServer).setVisibility(View.INVISIBLE); + + infoUpdate(root); + } else if(pageViewModel.getIndex() == 2) { + root.findViewById(R.id.layoutInfo).setVisibility(View.INVISIBLE); + root.findViewById(R.id.layoutNetwork).setVisibility(View.VISIBLE); + root.findViewById(R.id.layoutServer).setVisibility(View.INVISIBLE); + + Button button = root.findViewById(R.id.buttonApplyNetwork); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + saveSettings("network"); + } + }); + netUpdate(root); + } else if(pageViewModel.getIndex() == 3) { + root.findViewById(R.id.layoutInfo).setVisibility(View.INVISIBLE); + root.findViewById(R.id.layoutNetwork).setVisibility(View.INVISIBLE); + root.findViewById(R.id.layoutServer).setVisibility(View.VISIBLE); + + Button button = root.findViewById(R.id.buttonApplyServer); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + saveSettings("server"); + } + }); + serverUpdate(root); + updateSvrUpdate(root); + } + pageViewModel.getText().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(@Nullable String s) { + textView.setText(s); + } + }); + return root; + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/rito/osicmanager/ui/main/SectionsPagerAdapter.java b/app/src/main/java/kr/co/rito/osicmanager/ui/main/SectionsPagerAdapter.java new file mode 100644 index 0000000..106e509 --- /dev/null +++ b/app/src/main/java/kr/co/rito/osicmanager/ui/main/SectionsPagerAdapter.java @@ -0,0 +1,46 @@ +package kr.co.rito.osicmanager.ui.main; + +import android.content.Context; + +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; + +import kr.co.rito.osicmanager.R; + +/** + * A [FragmentPagerAdapter] that returns a fragment corresponding to + * one of the sections/tabs/pages. + */ +public class SectionsPagerAdapter extends FragmentPagerAdapter { + + @StringRes + private static final int[] TAB_TITLES = new int[]{R.string.tab_text_1, R.string.tab_text_2, R.string.tab_text_3}; + private final Context mContext; + + public SectionsPagerAdapter(Context context, FragmentManager fm) { + super(fm); + mContext = context; + } + + @Override + public Fragment getItem(int position) { + // getItem is called to instantiate the fragment for the given page. + // Return a PlaceholderFragment (defined as a static inner class below). + return PlaceholderFragment.newInstance(position + 1); + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + return mContext.getResources().getString(TAB_TITLES[position]); + } + + @Override + public int getCount() { + // Show 2 total pages. + return 3; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/eclipse/paho/android/service/AlarmPingSender.java b/app/src/main/java/org/eclipse/paho/android/service/AlarmPingSender.java new file mode 100644 index 0000000..8ad9569 --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/AlarmPingSender.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.paho.android.service; + +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttToken; +import org.eclipse.paho.client.mqttv3.MqttPingSender; +import org.eclipse.paho.client.mqttv3.internal.ClientComms; + +import android.annotation.SuppressLint; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Build; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.util.Log; + +/** + * Default ping sender implementation on Android. It is based on AlarmManager. + * + *

This class implements the {@link MqttPingSender} pinger interface + * allowing applications to send ping packet to server every keep alive interval. + *

+ * + * @see MqttPingSender + */ +class AlarmPingSender implements MqttPingSender { + // Identifier for Intents, log messages, etc.. + private static final String TAG = "AlarmPingSender"; + + // TODO: Add log. + private ClientComms comms; + private MqttService service; + private BroadcastReceiver alarmReceiver; + private AlarmPingSender that; + private PendingIntent pendingIntent; + private volatile boolean hasStarted = false; + + public AlarmPingSender(MqttService service) { + if (service == null) { + throw new IllegalArgumentException( + "Neither service nor client can be null."); + } + this.service = service; + that = this; + } + + @Override + public void init(ClientComms comms) { + this.comms = comms; + this.alarmReceiver = new AlarmReceiver(); + } + + @Override + public void start() { + String action = MqttServiceConstants.PING_SENDER + + comms.getClient().getClientId(); + Log.d(TAG, "Register alarmreceiver to MqttService"+ action); + service.registerReceiver(alarmReceiver, new IntentFilter(action)); + + pendingIntent = PendingIntent.getBroadcast(service, 0, new Intent( + action), PendingIntent.FLAG_UPDATE_CURRENT); + + schedule(comms.getKeepAlive()); + hasStarted = true; + } + + @Override + public void stop() { + + Log.d(TAG, "Unregister alarmreceiver to MqttService"+comms.getClient().getClientId()); + if(hasStarted){ + if(pendingIntent != null){ + // Cancel Alarm. + AlarmManager alarmManager = (AlarmManager) service.getSystemService(Service.ALARM_SERVICE); + alarmManager.cancel(pendingIntent); + } + + hasStarted = false; + try{ + service.unregisterReceiver(alarmReceiver); + }catch(IllegalArgumentException e){ + //Ignore unregister errors. + } + } + } + + @Override + public void schedule(long delayInMilliseconds) { + long nextAlarmInMilliseconds = System.currentTimeMillis() + + delayInMilliseconds; + Log.d(TAG, "Schedule next alarm at " + nextAlarmInMilliseconds); + AlarmManager alarmManager = (AlarmManager) service + .getSystemService(Service.ALARM_SERVICE); + + if(Build.VERSION.SDK_INT >= 23){ + // In SDK 23 and above, dosing will prevent setExact, setExactAndAllowWhileIdle will force + // the device to run this task whilst dosing. + Log.d(TAG, "Alarm scheule using setExactAndAllowWhileIdle, next: " + delayInMilliseconds); + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds, + pendingIntent); + } else if (Build.VERSION.SDK_INT >= 19) { + Log.d(TAG, "Alarm scheule using setExact, delay: " + delayInMilliseconds); + alarmManager.setExact(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds, + pendingIntent); + } else { + alarmManager.set(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds, + pendingIntent); + } + } + + /* + * This class sends PingReq packet to MQTT broker + */ + class AlarmReceiver extends BroadcastReceiver { + private WakeLock wakelock; + private final String wakeLockTag = MqttServiceConstants.PING_WAKELOCK + + that.comms.getClient().getClientId(); + + @Override + @SuppressLint("Wakelock") + public void onReceive(Context context, Intent intent) { + // According to the docs, "Alarm Manager holds a CPU wake lock as + // long as the alarm receiver's onReceive() method is executing. + // This guarantees that the phone will not sleep until you have + // finished handling the broadcast.", but this class still get + // a wake lock to wait for ping finished. + + Log.d(TAG, "Sending Ping at:" + System.currentTimeMillis()); + + PowerManager pm = (PowerManager) service + .getSystemService(Service.POWER_SERVICE); + wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag); + wakelock.acquire(); + + // Assign new callback to token to execute code after PingResq + // arrives. Get another wakelock even receiver already has one, + // release it until ping response returns. + IMqttToken token = comms.checkForActivity(new IMqttActionListener() { + + @Override + public void onSuccess(IMqttToken asyncActionToken) { + Log.d(TAG, "Success. Release lock(" + wakeLockTag + "):" + + System.currentTimeMillis()); + //Release wakelock when it is done. + wakelock.release(); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, + Throwable exception) { + Log.d(TAG, "Failure. Release lock(" + wakeLockTag + "):" + + System.currentTimeMillis()); + //Release wakelock when it is done. + wakelock.release(); + } + }); + + + if (token == null && wakelock.isHeld()) { + wakelock.release(); + } + } + } +} diff --git a/app/src/main/java/org/eclipse/paho/android/service/DatabaseMessageStore.java b/app/src/main/java/org/eclipse/paho/android/service/DatabaseMessageStore.java new file mode 100644 index 0000000..e11eca1 --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/DatabaseMessageStore.java @@ -0,0 +1,462 @@ +/******************************************************************************* + * Copyright (c) 1999, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * James Sutton - Removing SQL Injection vunerability (bug 467378) + */ +package org.eclipse.paho.android.service; + +import java.util.Iterator; + +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +/** + * Implementation of the {@link MessageStore} interface, using a SQLite database + * + */ +class DatabaseMessageStore implements MessageStore { + + // TAG used for indentify trace data etc. + private static final String TAG = "DatabaseMessageStore"; + + // One "private" database column name + // The other database column names are defined in MqttServiceConstants + private static final String MTIMESTAMP = "mtimestamp"; + + // the name of the table in the database to which we will save messages + private static final String ARRIVED_MESSAGE_TABLE_NAME = "MqttArrivedMessageTable"; + + // the database + private SQLiteDatabase db = null; + + // a SQLiteOpenHelper specific for this database + private MQTTDatabaseHelper mqttDb = null; + + // a place to send trace data + private MqttTraceHandler traceHandler = null; + + /** + * We need a SQLiteOpenHelper to handle database creation and updating + * + */ + private static class MQTTDatabaseHelper extends SQLiteOpenHelper { + // TAG used for indentify trace data etc. + private static final String TAG = "MQTTDatabaseHelper"; + + private static final String DATABASE_NAME = "mqttAndroidService.db"; + + // database version, used to recognise when we need to upgrade + // (delete and recreate) + private static final int DATABASE_VERSION = 1; + + // a place to send trace data + private MqttTraceHandler traceHandler = null; + + /** + * Constructor. + * + * @param traceHandler + * @param context + */ + public MQTTDatabaseHelper(MqttTraceHandler traceHandler, Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + this.traceHandler = traceHandler; + } + + /** + * When the database is (re)created, create our table + * + * @param database + */ + @Override + public void onCreate(SQLiteDatabase database) { + String createArrivedTableStatement = "CREATE TABLE " + + ARRIVED_MESSAGE_TABLE_NAME + "(" + + MqttServiceConstants.MESSAGE_ID + " TEXT PRIMARY KEY, " + + MqttServiceConstants.CLIENT_HANDLE + " TEXT, " + + MqttServiceConstants.DESTINATION_NAME + " TEXT, " + + MqttServiceConstants.PAYLOAD + " BLOB, " + + MqttServiceConstants.QOS + " INTEGER, " + + MqttServiceConstants.RETAINED + " TEXT, " + + MqttServiceConstants.DUPLICATE + " TEXT, " + MTIMESTAMP + + " INTEGER" + ");"; + traceHandler.traceDebug(TAG, "onCreate {" + + createArrivedTableStatement + "}"); + try { + database.execSQL(createArrivedTableStatement); + traceHandler.traceDebug(TAG, "created the table"); + } catch (SQLException e) { + traceHandler.traceException(TAG, "onCreate", e); + throw e; + } + } + + /** + * To upgrade the database, drop and recreate our table + * + * @param db + * the database + * @param oldVersion + * ignored + * @param newVersion + * ignored + */ + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + traceHandler.traceDebug(TAG, "onUpgrade"); + try { + db.execSQL("DROP TABLE IF EXISTS " + ARRIVED_MESSAGE_TABLE_NAME); + } catch (SQLException e) { + traceHandler.traceException(TAG, "onUpgrade", e); + throw e; + } + onCreate(db); + traceHandler.traceDebug(TAG, "onUpgrade complete"); + } + } + + /** + * Constructor - create a DatabaseMessageStore to store arrived MQTT message + * + * @param service + * our parent MqttService + * @param context + * a context to use for android calls + */ + public DatabaseMessageStore(MqttService service, Context context) { + this.traceHandler = service; + + // Open message database + mqttDb = new MQTTDatabaseHelper(traceHandler, context); + + // Android documentation suggests that this perhaps + // could/should be done in another thread, but as the + // database is only one table, I doubt it matters... + + traceHandler.traceDebug(TAG, "DatabaseMessageStore complete"); + } + + /** + * Store an MQTT message + * + * @param clientHandle + * identifier for the client storing the message + * @param topic + * The topic on which the message was published + * @param message + * the arrived MQTT message + * @return an identifier for the message, so that it can be removed when appropriate + */ + @Override + public String storeArrived(String clientHandle, String topic, + MqttMessage message) { + + db = mqttDb.getWritableDatabase(); + + traceHandler.traceDebug(TAG, "storeArrived{" + clientHandle + "}, {" + + message.toString() + "}"); + + byte[] payload = message.getPayload(); + int qos = message.getQos(); + boolean retained = message.isRetained(); + boolean duplicate = message.isDuplicate(); + + ContentValues values = new ContentValues(); + String id = java.util.UUID.randomUUID().toString(); + values.put(MqttServiceConstants.MESSAGE_ID, id); + values.put(MqttServiceConstants.CLIENT_HANDLE, clientHandle); + values.put(MqttServiceConstants.DESTINATION_NAME, topic); + values.put(MqttServiceConstants.PAYLOAD, payload); + values.put(MqttServiceConstants.QOS, qos); + values.put(MqttServiceConstants.RETAINED, retained); + values.put(MqttServiceConstants.DUPLICATE, duplicate); + values.put(MTIMESTAMP, System.currentTimeMillis()); + try { + db.insertOrThrow(ARRIVED_MESSAGE_TABLE_NAME, null, values); + } catch (SQLException e) { + traceHandler.traceException(TAG, "onUpgrade", e); + throw e; + } + int count = getArrivedRowCount(clientHandle); + traceHandler + .traceDebug( + TAG, + "storeArrived: inserted message with id of {" + + id + + "} - Number of messages in database for this clientHandle = " + + count); + return id; + } + + private int getArrivedRowCount(String clientHandle) { + int count = 0; + String[] projection = { + MqttServiceConstants.MESSAGE_ID, + }; + String selection = MqttServiceConstants.CLIENT_HANDLE + "=?"; + String[] selectionArgs = new String[1]; + selectionArgs[0] = clientHandle; + Cursor c = db.query( + ARRIVED_MESSAGE_TABLE_NAME, // Table Name + projection, // The columns to return; + selection, // Columns for WHERE Clause + selectionArgs , // The values for the WHERE Cause + null, //Don't group the rows + null, // Don't filter by row groups + null // The sort order + ); + + if (c.moveToFirst()) { + count = c.getInt(0); + } + c.close(); + return count; + } + + /** + * Delete an MQTT message. + * + * @param clientHandle + * identifier for the client which stored the message + * @param id + * the identifying string returned when the message was stored + * + * @return true if the message was found and deleted + */ + @Override + public boolean discardArrived(String clientHandle, String id) { + + db = mqttDb.getWritableDatabase(); + + traceHandler.traceDebug(TAG, "discardArrived{" + clientHandle + "}, {" + + id + "}"); + int rows; + String[] selectionArgs = new String[2]; + selectionArgs[0] = id; + selectionArgs[1] = clientHandle; + + try { + rows = db.delete(ARRIVED_MESSAGE_TABLE_NAME, + MqttServiceConstants.MESSAGE_ID + "=? AND " + + MqttServiceConstants.CLIENT_HANDLE + "=?", + selectionArgs); + } catch (SQLException e) { + traceHandler.traceException(TAG, "discardArrived", e); + throw e; + } + if (rows != 1) { + traceHandler.traceError(TAG, + "discardArrived - Error deleting message {" + id + + "} from database: Rows affected = " + rows); + return false; + } + int count = getArrivedRowCount(clientHandle); + traceHandler + .traceDebug( + TAG, + "discardArrived - Message deleted successfully. - messages in db for this clientHandle " + + count); + return true; + } + + /** + * Get an iterator over all messages stored (optionally for a specific client) + * + * @param clientHandle + * identifier for the client.
+ * If null, all messages are retrieved + * @return iterator of all the arrived MQTT messages + */ + @Override + public Iterator getAllArrivedMessages( + final String clientHandle) { + return new Iterator() { + private Cursor c; + private boolean hasNext; + private final String[] selectionArgs = { + clientHandle, + }; + + + { + db = mqttDb.getWritableDatabase(); + // anonymous initialiser to start a suitable query + // and position at the first row, if one exists + if (clientHandle == null) { + c = db.query(ARRIVED_MESSAGE_TABLE_NAME, + null, + null, + null, + null, + null, + "mtimestamp ASC"); + } else { + c = db.query(ARRIVED_MESSAGE_TABLE_NAME, + null, + MqttServiceConstants.CLIENT_HANDLE + "=?", + selectionArgs, + null, + null, + "mtimestamp ASC"); + } + hasNext = c.moveToFirst(); + } + + @Override + public boolean hasNext() { + if (!hasNext){ + c.close(); + } + return hasNext; + } + + @Override + public StoredMessage next() { + String messageId = c.getString(c + .getColumnIndex(MqttServiceConstants.MESSAGE_ID)); + String clientHandle = c.getString(c + .getColumnIndex(MqttServiceConstants.CLIENT_HANDLE)); + String topic = c.getString(c + .getColumnIndex(MqttServiceConstants.DESTINATION_NAME)); + byte[] payload = c.getBlob(c + .getColumnIndex(MqttServiceConstants.PAYLOAD)); + int qos = c.getInt(c.getColumnIndex(MqttServiceConstants.QOS)); + boolean retained = Boolean.parseBoolean(c.getString(c + .getColumnIndex(MqttServiceConstants.RETAINED))); + boolean dup = Boolean.parseBoolean(c.getString(c + .getColumnIndex(MqttServiceConstants.DUPLICATE))); + + // build the result + MqttMessageHack message = new MqttMessageHack(payload); + message.setQos(qos); + message.setRetained(retained); + message.setDuplicate(dup); + + // move on + hasNext = c.moveToNext(); + return new DbStoredData(messageId, clientHandle, topic, message); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see java.lang.Object#finalize() + */ + @Override + protected void finalize() throws Throwable { + c.close(); + super.finalize(); + } + + }; + } + + /** + * Delete all messages (optionally for a specific client) + * + * @param clientHandle + * identifier for the client.
+ * If null, all messages are deleted + */ + @Override + public void clearArrivedMessages(String clientHandle) { + + db = mqttDb.getWritableDatabase(); + String[] selectionArgs = new String[1]; + selectionArgs[0] = clientHandle; + + int rows = 0; + if (clientHandle == null) { + traceHandler.traceDebug(TAG, + "clearArrivedMessages: clearing the table"); + rows = db.delete(ARRIVED_MESSAGE_TABLE_NAME, null, null); + } else { + traceHandler.traceDebug(TAG, + "clearArrivedMessages: clearing the table of " + + clientHandle + " messages"); + rows = db.delete(ARRIVED_MESSAGE_TABLE_NAME, + MqttServiceConstants.CLIENT_HANDLE + "=?", + selectionArgs); + + } + traceHandler.traceDebug(TAG, "clearArrivedMessages: rows affected = " + + rows); + } + + private class DbStoredData implements StoredMessage { + private String messageId; + private String clientHandle; + private String topic; + private MqttMessage message; + + DbStoredData(String messageId, String clientHandle, String topic, + MqttMessage message) { + this.messageId = messageId; + this.topic = topic; + this.message = message; + } + + @Override + public String getMessageId() { + return messageId; + } + + @Override + public String getClientHandle() { + return clientHandle; + } + + @Override + public String getTopic() { + return topic; + } + + @Override + public MqttMessage getMessage() { + return message; + } + } + + /** + * A way to get at the "setDuplicate" method of MqttMessage + */ + private class MqttMessageHack extends MqttMessage { + + public MqttMessageHack(byte[] payload) { + super(payload); + } + + @Override + protected void setDuplicate(boolean dup) { + super.setDuplicate(dup); + } + } + + @Override + public void close() { + if (this.db!=null) + this.db.close(); + + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/eclipse/paho/android/service/MessageStore.java b/app/src/main/java/org/eclipse/paho/android/service/MessageStore.java new file mode 100644 index 0000000..b8ebd97 --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/MessageStore.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 1999, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.paho.android.service; + +import java.util.Iterator; + +import org.eclipse.paho.client.mqttv3.MqttMessage; + +/** + *

+ * Mechanism for persisting messages until we know they have been received + *

+ *
    + *
  • A Service should store messages as they arrive via + * {@link #storeArrived(String, String, MqttMessage)}. + *
  • When a message has been passed to the consuming entity, + * {@link #discardArrived(String, String)} should be called. + *
  • To recover messages which have not been definitely passed to the + * consumer, {@link MessageStore#getAllArrivedMessages(String)} is used. + *
  • When a clean session is started {@link #clearArrivedMessages(String)} is + * used. + *
+ */ +interface MessageStore { + + /** + * External representation of a stored message + */ + interface StoredMessage { + /** + * @return the identifier for the message within the store + */ + String getMessageId(); + + /** + * @return the identifier of the client which stored this message + */ + String getClientHandle(); + + /** + * @return the topic on which the message was received + */ + String getTopic(); + + /** + * @return the identifier of the client which stored this message + */ + MqttMessage getMessage(); + } + + /** + * Store a message and return an identifier for it + * + * @param clientHandle + * identifier for the client + * @param message + * message to be stored + * @return a unique identifier for it + */ + String storeArrived(String clientHandle, String Topic, + MqttMessage message); + + /** + * Discard a message - called when we are certain that an arrived message + * has reached the application. + * + * @param clientHandle + * identifier for the client + * @param id + * id of message to be discarded + */ + boolean discardArrived(String clientHandle, String id); + + /** + * Get all the stored messages, usually for a specific client + * + * @param clientHandle + * identifier for the client - if null, then messages for all + * clients are returned + */ + Iterator getAllArrivedMessages(String clientHandle); + + /** + * Discard stored messages, usually for a specific client + * + * @param clientHandle + * identifier for the client - if null, then messages for all + * clients are discarded + */ + void clearArrivedMessages(String clientHandle); + + void close(); +} diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttAndroidClient.java b/app/src/main/java/org/eclipse/paho/android/service/MqttAndroidClient.java new file mode 100644 index 0000000..6641277 --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/MqttAndroidClient.java @@ -0,0 +1,1764 @@ +/******************************************************************************* + * Copyright (c) 1999, 2016 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Ian Craggs - Per subscription message handlers bug 466579 + * Ian Craggs - ack control (bug 472172) + * + */ +package org.eclipse.paho.android.service; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttAsyncClient; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.IMqttMessageListener; +import org.eclipse.paho.client.mqttv3.IMqttToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; +import org.eclipse.paho.client.mqttv3.MqttClientPersistence; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.MqttPersistenceException; +import org.eclipse.paho.client.mqttv3.MqttSecurityException; +import org.eclipse.paho.client.mqttv3.MqttToken; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import android.util.SparseArray; + +/** + * Enables an android application to communicate with an MQTT server using non-blocking methods. + *

+ * Implementation of the MQTT asynchronous client interface {@link IMqttAsyncClient} , using the MQTT + * android service to actually interface with MQTT server. It provides android applications a simple programming interface to all features of the MQTT version 3.1 + * specification including: + *

+ *
    + *
  • connect + *
  • publish + *
  • subscribe + *
  • unsubscribe + *
  • disconnect + *
+ */ +public class MqttAndroidClient extends BroadcastReceiver implements + IMqttAsyncClient { + + /** + * + * The Acknowledgment mode for messages received from {@link MqttCallback#messageArrived(String, MqttMessage)} + * + */ + public enum Ack { + /** + * As soon as the {@link MqttCallback#messageArrived(String, MqttMessage)} returns, + * the message has been acknowledged as received . + */ + AUTO_ACK, + /** + * When {@link MqttCallback#messageArrived(String, MqttMessage)} returns, the message + * will not be acknowledged as received, the application will have to make an acknowledgment call + * to {@link MqttAndroidClient} using {@link MqttAndroidClient#acknowledgeMessage(String)} + */ + MANUAL_ACK + } + + private static final String SERVICE_NAME = "org.eclipse.paho.android.service.MqttService"; + + private static final int BIND_SERVICE_FLAG = 0; + + private static final ExecutorService pool = Executors.newCachedThreadPool(); + + /** + * ServiceConnection to process when we bind to our service + */ + private final class MyServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + mqttService = ((MqttServiceBinder) binder).getService(); + bindedService = true; + // now that we have the service available, we can actually + // connect... + doConnect(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mqttService = null; + } + } + + // Listener for when the service is connected or disconnected + private final MyServiceConnection serviceConnection = new MyServiceConnection(); + + // The Android Service which will process our mqtt calls + private MqttService mqttService; + + // An identifier for the underlying client connection, which we can pass to + // the service + private String clientHandle; + + private Context myContext; + + // We hold the various tokens in a collection and pass identifiers for them + // to the service + private final SparseArray tokenMap = new SparseArray<>(); + private int tokenNumber = 0; + + // Connection data + private final String serverURI; + private final String clientId; + private MqttClientPersistence persistence = null; + private MqttConnectOptions connectOptions; + private IMqttToken connectToken; + + // The MqttCallback provided by the application + private MqttCallback callback; + private MqttTraceHandler traceCallback; + + //The acknowledgment that a message has been processed by the application + private final Ack messageAck; + private boolean traceEnabled = false; + + private volatile boolean receiverRegistered = false; + private volatile boolean bindedService = false; + + /** + * Constructor - create an MqttAndroidClient that can be used to communicate with an MQTT server on android + * + * @param context + * object used to pass context to the callback. + * @param serverURI + * specifies the protocol, host name and port to be used to + * connect to an MQTT server + * @param clientId + * specifies the name by which this connection should be + * identified to the server + */ + public MqttAndroidClient(Context context, String serverURI, + String clientId) { + this(context, serverURI, clientId, null, Ack.AUTO_ACK); + } + + /** + * Constructor - create an MqttAndroidClient that can be used to communicate + * with an MQTT server on android + * + * @param ctx + * Application's context + * @param serverURI + * specifies the protocol, host name and port to be used to + * connect to an MQTT server + * @param clientId + * specifies the name by which this connection should be + * identified to the server + * @param ackType + * how the application wishes to acknowledge a message has been + * processed + */ + public MqttAndroidClient(Context ctx, String serverURI, String clientId, Ack ackType) { + this(ctx, serverURI, clientId, null, ackType); + } + + /** + * Constructor - create an MqttAndroidClient that can be used to communicate + * with an MQTT server on android + * + * @param ctx + * Application's context + * @param serverURI + * specifies the protocol, host name and port to be used to + * connect to an MQTT server + * @param clientId + * specifies the name by which this connection should be + * identified to the server + * @param persistence + * The object to use to store persisted data + */ + public MqttAndroidClient(Context ctx, String serverURI, String clientId, MqttClientPersistence persistence) { + this(ctx, serverURI, clientId, persistence, Ack.AUTO_ACK); + } + + /** + * Constructor- create an MqttAndroidClient that can be used to communicate + * with an MQTT server on android + * + * @param context + * used to pass context to the callback. + * @param serverURI + * specifies the protocol, host name and port to be used to + * connect to an MQTT server + * @param clientId + * specifies the name by which this connection should be + * identified to the server + * @param persistence + * the persistence class to use to store in-flight message. If + * null then the default persistence mechanism is used + * @param ackType + * how the application wishes to acknowledge a message has been + * processed. + */ + public MqttAndroidClient(Context context, String serverURI, + String clientId, MqttClientPersistence persistence, Ack ackType) { + myContext = context; + this.serverURI = serverURI; + this.clientId = clientId; + this.persistence = persistence; + messageAck = ackType; + } + + /** + * Determines if this client is currently connected to the server. + * + * @return true if connected, false otherwise. + */ + @Override + public boolean isConnected() { + + return clientHandle != null && mqttService != null && mqttService.isConnected(clientHandle); + } + + /** + * Returns the client ID used by this client. + *

+ * All clients connected to the same server or server farm must have a + * unique ID. + *

+ * + * @return the client ID used by this client. + */ + @Override + public String getClientId() { + return clientId; + } + + /** + * Returns the URI address of the server used by this client. + *

+ * The format of the returned String is the same as that used on the + * constructor. + *

+ * + * @return the server's address, as a URI String. + */ + @Override + public String getServerURI() { + return serverURI; + } + + /** + * Close the client. Releases all resource associated with the client. After + * the client has been closed it cannot be reused. For instance attempts to + * connect will fail. + * + */ + @Override + public void close() { + if(mqttService != null){ + if (clientHandle == null) { + clientHandle = mqttService.getClient(serverURI, clientId, myContext.getApplicationInfo().packageName,persistence); + } + mqttService.close(clientHandle); + } + } + + /** + * Connects to an MQTT server using the default options. + *

+ * The default options are specified in {@link MqttConnectOptions} class. + *

+ * + * @throws MqttException + * for any connected problems + * @return token used to track and wait for the connect to complete. The + * token will be passed to the callback methods if a callback is + * set. + * @see #connect(MqttConnectOptions, Object, IMqttActionListener) + */ + @Override + public IMqttToken connect() throws MqttException { + return connect(null, null); + } + + + /** + * Connects to an MQTT server using the provided connect options. + *

+ * The connection will be established using the options specified in the + * {@link MqttConnectOptions} parameter. + *

+ * + * @param options + * a set of connection parameters that override the defaults. + * @throws MqttException + * for any connected problems + * @return token used to track and wait for the connect to complete. The + * token will be passed to any callback that has been set. + * @see #connect(MqttConnectOptions, Object, IMqttActionListener) + */ + @Override + public IMqttToken connect(MqttConnectOptions options) throws MqttException { + return connect(options, null, null); + } + + /** + * Connects to an MQTT server using the default options. + *

+ * The default options are specified in {@link MqttConnectOptions} class. + *

+ * + * @param userContext + * optional object used to pass context to the callback. Use null + * if not required. + * @param callback + * optional listener that will be notified when the connect + * completes. Use null if not required. + * @throws MqttException + * for any connected problems + * @return token used to track and wait for the connect to complete. The + * token will be passed to any callback that has been set. + * @see #connect(MqttConnectOptions, Object, IMqttActionListener) + */ + @Override + public IMqttToken connect(Object userContext, IMqttActionListener callback) + throws MqttException { + return connect(new MqttConnectOptions(), userContext, callback); + } + + /** + * Connects to an MQTT server using the specified options. + *

+ * The server to connect to is specified on the constructor. It is + * recommended to call {@link #setCallback(MqttCallback)} prior to + * connecting in order that messages destined for the client can be accepted + * as soon as the client is connected. + *

+ * + *

+ * The method returns control before the connect completes. Completion can + * be tracked by: + *

+ *
    + *
  • Waiting on the returned token {@link IMqttToken#waitForCompletion()} + * or
  • + *
  • Passing in a callback {@link IMqttActionListener}
  • + *
+ * + * + * @param options + * a set of connection parameters that override the defaults. + * @param userContext + * optional object for used to pass context to the callback. Use + * null if not required. + * @param callback + * optional listener that will be notified when the connect + * completes. Use null if not required. + * @return token used to track and wait for the connect to complete. The + * token will be passed to any callback that has been set. + * @throws MqttException + * for any connected problems, including communication errors + */ + + @Override + public IMqttToken connect(MqttConnectOptions options, Object userContext, + IMqttActionListener callback) throws MqttException { + + IMqttToken token = new MqttTokenAndroid(this, userContext, + callback); + + connectOptions = options; + connectToken = token; + + /* + * The actual connection depends on the service, which we start and bind + * to here, but which we can't actually use until the serviceConnection + * onServiceConnected() method has run (asynchronously), so the + * connection itself takes place in the onServiceConnected() method + */ + if (mqttService == null) { // First time - must bind to the service + Intent serviceStartIntent = new Intent(); + serviceStartIntent.setClassName(myContext, SERVICE_NAME); + Object service = myContext.startService(serviceStartIntent); + if (service == null) { + IMqttActionListener listener = token.getActionCallback(); + if (listener != null) { + listener.onFailure(token, new RuntimeException( + "cannot start service " + SERVICE_NAME)); + } + } + + // We bind with BIND_SERVICE_FLAG (0), leaving us the manage the lifecycle + // until the last time it is stopped by a call to stopService() + myContext.bindService(serviceStartIntent, serviceConnection, + Context.BIND_AUTO_CREATE); + + if (!receiverRegistered) registerReceiver(this); + } + else { + pool.execute(new Runnable() { + + @Override + public void run() { + doConnect(); + + //Register receiver to show shoulder tap. + if (!receiverRegistered) registerReceiver(MqttAndroidClient.this); + } + + }); + } + + return token; + } + + private void registerReceiver(BroadcastReceiver receiver) { + IntentFilter filter = new IntentFilter(); + filter.addAction(MqttServiceConstants.CALLBACK_TO_ACTIVITY); + LocalBroadcastManager.getInstance(myContext).registerReceiver(receiver, filter); + receiverRegistered = true; + } + + /** + * Actually do the mqtt connect operation + */ + private void doConnect() { + if (clientHandle == null) { + clientHandle = mqttService.getClient(serverURI, clientId,myContext.getApplicationInfo().packageName, + persistence); + } + mqttService.setTraceEnabled(traceEnabled); + mqttService.setTraceCallbackId(clientHandle); + + String activityToken = storeToken(connectToken); + try { + mqttService.connect(clientHandle, connectOptions, null, + activityToken); + } + catch (MqttException e) { + IMqttActionListener listener = connectToken.getActionCallback(); + if (listener != null) { + listener.onFailure(connectToken, e); + } + } + } + + /** + * Disconnects from the server. + *

+ * An attempt is made to quiesce the client allowing outstanding work to + * complete before disconnecting. It will wait for a maximum of 30 seconds + * for work to quiesce before disconnecting. This method must not be called + * from inside {@link MqttCallback} methods. + *

+ * + * @return token used to track and wait for disconnect to complete. The + * token will be passed to any callback that has been set. + * @throws MqttException + * for problems encountered while disconnecting + * @see #disconnect(long, Object, IMqttActionListener) + */ + @Override + public IMqttToken disconnect() throws MqttException { + IMqttToken token = new MqttTokenAndroid(this, null, + null); + String activityToken = storeToken(token); + mqttService.disconnect(clientHandle, null, activityToken); + return token; + } + + /** + * Disconnects from the server. + *

+ * An attempt is made to quiesce the client allowing outstanding work to + * complete before disconnecting. It will wait for a maximum of the + * specified quiesce time for work to complete before disconnecting. This + * method must not be called from inside {@link MqttCallback} methods. + *

+ * + * @param quiesceTimeout + * the amount of time in milliseconds to allow for existing work + * to finish before disconnecting. A value of zero or less means + * the client will not quiesce. + * @return token used to track and wait for disconnect to complete. The + * token will be passed to the callback methods if a callback is + * set. + * @throws MqttException + * for problems encountered while disconnecting + * @see #disconnect(long, Object, IMqttActionListener) + */ + @Override + public IMqttToken disconnect(long quiesceTimeout) throws MqttException { + IMqttToken token = new MqttTokenAndroid(this, null, + null); + String activityToken = storeToken(token); + mqttService.disconnect(clientHandle, quiesceTimeout, null, + activityToken); + return token; + } + + /** + * Disconnects from the server. + *

+ * An attempt is made to quiesce the client allowing outstanding work to + * complete before disconnecting. It will wait for a maximum of 30 seconds + * for work to quiesce before disconnecting. This method must not be called + * from inside {@link MqttCallback} methods. + *

+ * + * @param userContext + * optional object used to pass context to the callback. Use null + * if not required. + * @param callback + * optional listener that will be notified when the disconnect + * completes. Use null if not required. + * @return token used to track and wait for the disconnect to complete. The + * token will be passed to any callback that has been set. + * @throws MqttException + * for problems encountered while disconnecting + * @see #disconnect(long, Object, IMqttActionListener) + */ + @Override + public IMqttToken disconnect(Object userContext, + IMqttActionListener callback) throws MqttException { + IMqttToken token = new MqttTokenAndroid(this, userContext, + callback); + String activityToken = storeToken(token); + mqttService.disconnect(clientHandle, null, activityToken); + return token; + } + + /** + * Disconnects from the server. + *

+ * The client will wait for {@link MqttCallback} methods to complete. It + * will then wait for up to the quiesce timeout to allow for work which has + * already been initiated to complete. For instance when a QoS 2 message has + * started flowing to the server but the QoS 2 flow has not completed.It + * prevents new messages being accepted and does not send any messages that + * have been accepted but not yet started delivery across the network to the + * server. When work has completed or after the quiesce timeout, the client + * will disconnect from the server. If the cleanSession flag was set to + * false and next time it is also set to false in the connection, the + * messages made in QoS 1 or 2 which were not previously delivered will be + * delivered this time. + *

+ *

+ * This method must not be called from inside {@link MqttCallback} methods. + *

+ *

+ * The method returns control before the disconnect completes. Completion + * can be tracked by: + *

+ *
    + *
  • Waiting on the returned token {@link IMqttToken#waitForCompletion()} + * or
  • + *
  • Passing in a callback {@link IMqttActionListener}
  • + *
+ * + * @param quiesceTimeout + * the amount of time in milliseconds to allow for existing work + * to finish before disconnecting. A value of zero or less means + * the client will not quiesce. + * @param userContext + * optional object used to pass context to the callback. Use null + * if not required. + * @param callback + * optional listener that will be notified when the disconnect + * completes. Use null if not required. + * @return token used to track and wait for the disconnect to complete. The + * token will be passed to any callback that has been set. + * @throws MqttException + * for problems encountered while disconnecting + */ + @Override + public IMqttToken disconnect(long quiesceTimeout, Object userContext, + IMqttActionListener callback) throws MqttException { + IMqttToken token = new MqttTokenAndroid(this, userContext, + callback); + String activityToken = storeToken(token); + mqttService.disconnect(clientHandle, quiesceTimeout, null, + activityToken); + return token; + } + + /** + * Publishes a message to a topic on the server. + *

+ * A convenience method, which will create a new {@link MqttMessage} object + * with a byte array payload and the specified QoS, and then publish it. + *

+ * + * @param topic + * to deliver the message to, for example "finance/stock/ibm". + * @param payload + * the byte array to use as the payload + * @param qos + * the Quality of Service to deliver the message at. Valid values + * are 0, 1 or 2. + * @param retained + * whether or not this message should be retained by the server. + * @return token used to track and wait for the publish to complete. The + * token will be passed to any callback that has been set. + * @throws MqttPersistenceException + * when a problem occurs storing the message + * @throws IllegalArgumentException + * if value of QoS is not 0, 1 or 2. + * @throws MqttException + * for other errors encountered while publishing the message. + * For instance, too many messages are being processed. + * @see #publish(String, MqttMessage, Object, IMqttActionListener) + */ + @Override + public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, + boolean retained) throws MqttException, MqttPersistenceException { + return publish(topic, payload, qos, retained, null, null); + } + + /** + * Publishes a message to a topic on the server. Takes an + * {@link MqttMessage} message and delivers it to the server at the + * requested quality of service. + * + * @param topic + * to deliver the message to, for example "finance/stock/ibm". + * @param message + * to deliver to the server + * @return token used to track and wait for the publish to complete. The + * token will be passed to any callback that has been set. + * @throws MqttPersistenceException + * when a problem occurs storing the message + * @throws IllegalArgumentException + * if value of QoS is not 0, 1 or 2. + * @throws MqttException + * for other errors encountered while publishing the message. + * For instance client not connected. + * @see #publish(String, MqttMessage, Object, IMqttActionListener) + */ + @Override + public IMqttDeliveryToken publish(String topic, MqttMessage message) + throws MqttException, MqttPersistenceException { + return publish(topic, message, null, null); + } + + /** + * Publishes a message to a topic on the server. + *

+ * A convenience method, which will create a new {@link MqttMessage} object + * with a byte array payload, the specified QoS and retained, then publish it. + *

+ * + * @param topic + * to deliver the message to, for example "finance/stock/ibm". + * @param payload + * the byte array to use as the payload + * @param qos + * the Quality of Service to deliver the message at. Valid values + * are 0, 1 or 2. + * @param retained + * whether or not this message should be retained by the server. + * @param userContext + * optional object used to pass context to the callback. Use null + * if not required. + * @param callback + * optional listener that will be notified when message delivery + * has completed to the requested quality of service + * @return token used to track and wait for the publish to complete. The + * token will be passed to any callback that has been set. + * @throws MqttPersistenceException + * when a problem occurs storing the message + * @throws IllegalArgumentException + * if value of QoS is not 0, 1 or 2. + * @throws MqttException + * for other errors encountered while publishing the message. + * For instance client not connected. + * @see #publish(String, MqttMessage, Object, IMqttActionListener) + */ + @Override + public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, + boolean retained, Object userContext, IMqttActionListener callback) + throws MqttException, MqttPersistenceException { + + MqttMessage message = new MqttMessage(payload); + message.setQos(qos); + message.setRetained(retained); + MqttDeliveryTokenAndroid token = new MqttDeliveryTokenAndroid( + this, userContext, callback, message); + String activityToken = storeToken(token); + IMqttDeliveryToken internalToken = mqttService.publish(clientHandle, + topic, payload, qos, retained, null, activityToken); + token.setDelegate(internalToken); + return token; + } + + /** + * Publishes a message to a topic on the server. + *

+ * Once this method has returned cleanly, the message has been accepted for + * publication by the client and will be delivered on a background thread. + * In the event the connection fails or the client stops, Messages will be + * delivered to the requested quality of service once the connection is + * re-established to the server on condition that: + *

+ *
    + *
  • The connection is re-established with the same clientID + *
  • The original connection was made with (@link + * MqttConnectOptions#setCleanSession(boolean)} set to false + *
  • The connection is re-established with (@link + * MqttConnectOptions#setCleanSession(boolean)} set to false + *
  • Depending when the failure occurs QoS 0 messages may not be + * delivered. + *
+ * + *

+ * When building an application, the design of the topic tree should take + * into account the following principles of topic name syntax and semantics: + *

+ * + *
    + *
  • A topic must be at least one character long.
  • + *
  • Topic names are case sensitive. For example, ACCOUNTS and + * Accounts are two different topics.
  • + *
  • Topic names can include the space character. For example, + * Accounts + * payable is a valid topic.
  • + *
  • A leading "/" creates a distinct topic. For example, + * /finance is different from finance. /finance + * matches "+/+" and "/+", but not "+".
  • + *
  • Do not include the null character (Unicode \x0000) in any topic.
  • + *
+ * + *

+ * The following principles apply to the construction and content of a topic + * tree: + *

+ * + *
    + *
  • The length is limited to 64k but within that there are no limits to + * the number of levels in a topic tree.
  • + *
  • There can be any number of root nodes; that is, there can be any + * number of topic trees.
  • + *
+ *

+ * The method returns control before the publish completes. Completion can + * be tracked by: + *

+ *
    + *
  • Setting an {@link IMqttAsyncClient#setCallback(MqttCallback)} where + * the {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)} method will + * be called.
  • + *
  • Waiting on the returned token {@link MqttToken#waitForCompletion()} + * or
  • + *
  • Passing in a callback {@link IMqttActionListener} to this method
  • + *
+ * + * @param topic + * to deliver the message to, for example "finance/stock/ibm". + * @param message + * to deliver to the server + * @param userContext + * optional object used to pass context to the callback. Use null + * if not required. + * @param callback + * optional listener that will be notified when message delivery + * has completed to the requested quality of service + * @return token used to track and wait for the publish to complete. The + * token will be passed to callback methods if set. + * @throws MqttPersistenceException + * when a problem occurs storing the message + * @throws IllegalArgumentException + * if value of QoS is not 0, 1 or 2. + * @throws MqttException + * for other errors encountered while publishing the message. + * For instance, client not connected. + * @see MqttMessage + */ + @Override + public IMqttDeliveryToken publish(String topic, MqttMessage message, + Object userContext, IMqttActionListener callback) + throws MqttException, MqttPersistenceException { + MqttDeliveryTokenAndroid token = new MqttDeliveryTokenAndroid( + this, userContext, callback, message); + String activityToken = storeToken(token); + IMqttDeliveryToken internalToken = mqttService.publish(clientHandle, + topic, message, null, activityToken); + token.setDelegate(internalToken); + return token; + } + + /** + * Subscribe to a topic, which may include wildcards. + * + * @param topic + * the topic to subscribe to, which can include wildcards. + * @param qos + * the maximum quality of service at which to subscribe. Messages + * published at a lower quality of service will be received at + * the published QoS. Messages published at a higher quality of + * service will be received using the QoS specified on the + * subscription. + * @return token used to track and wait for the subscribe to complete. The + * token will be passed to callback methods if set. + * @throws MqttSecurityException + * for security related problems + * @throws MqttException + * for non security related problems + * + * @see #subscribe(String[], int[], Object, IMqttActionListener) + */ + @Override + public IMqttToken subscribe(String topic, int qos) throws MqttException, + MqttSecurityException { + return subscribe(topic, qos, null, null); + } + + /** + * Subscribe to multiple topics, each topic may include wildcards. + * + *

+ * Provides an optimized way to subscribe to multiple topics compared to + * subscribing to each one individually. + *

+ * + * @param topic + * one or more topics to subscribe to, which can include + * wildcards + * @param qos + * the maximum quality of service at which to subscribe. Messages + * published at a lower quality of service will be received at + * the published QoS. Messages published at a higher quality of + * service will be received using the QoS specified on the + * subscription. + * @return token used to track and wait for the subscription to complete. The + * token will be passed to callback methods if set. + * @throws MqttSecurityException + * for security related problems + * @throws MqttException + * for non security related problems + * + * @see #subscribe(String[], int[], Object, IMqttActionListener) + */ + @Override + public IMqttToken subscribe(String[] topic, int[] qos) + throws MqttException, MqttSecurityException { + return subscribe(topic, qos, null, null); + } + + /** + * Subscribe to a topic, which may include wildcards. + * + * @param topic + * the topic to subscribe to, which can include wildcards. + * @param qos + * the maximum quality of service at which to subscribe. Messages + * published at a lower quality of service will be received at + * the published QoS. Messages published at a higher quality of + * service will be received using the QoS specified on the + * subscription. + * @param userContext + * optional object used to pass context to the callback. Use null + * if not required. + * @param callback + * optional listener that will be notified when subscribe has + * completed + * @return token used to track and wait for the subscribe to complete. The + * token will be passed to callback methods if set. + * @throws MqttException + * if there was an error when registering the subscription. + * + * @see #subscribe(String[], int[], Object, IMqttActionListener) + */ + @Override + public IMqttToken subscribe(String topic, int qos, Object userContext, + IMqttActionListener callback) throws MqttException { + IMqttToken token = new MqttTokenAndroid(this, userContext, + callback, new String[]{topic}); + String activityToken = storeToken(token); + mqttService.subscribe(clientHandle, topic, qos, null, activityToken); + return token; + } + + /** + * Subscribes to multiple topics, each topic may include wildcards. + *

+ * Provides an optimized way to subscribe to multiple topics compared to + * subscribing to each one individually. + *

+ *

+ * The {@link #setCallback(MqttCallback)} method should be called before + * this method, otherwise any received messages will be discarded. + *

+ *

+ * If (@link MqttConnectOptions#setCleanSession(boolean)} was set to true, + * when connecting to the server, the subscription remains in place until + * either: + *

+ *
    + *
  • The client disconnects
  • + *
  • An unsubscribe method is called to unsubscribe the topic
  • + *
+ *

+ * If (@link MqttConnectOptions#setCleanSession(boolean)} was set to false, + * when connecting to the server, the subscription remains in place + * until either: + *

+ *
    + *
  • An unsubscribe method is called to unsubscribe the topic
  • + *
  • The next time the client connects with cleanSession set to true + *
+ *

With cleanSession set to false the MQTT server will store messages + * on behalf of the client when the client is not connected. The next time + * the client connects with the same client ID the server will + * deliver the stored messages to the client. + *

+ * + *

+ * The "topic filter" string is used when subscription may contain special + * characters, which allows you to subscribe to multiple topics at once. + *

+ *
Topic level separator
+ *
The forward slash (/) is used to separate each level within a topic + * tree and provide a hierarchical structure to the topic space. The use of + * the topic level separator is significant when the two wildcard characters + * are encountered in topics specified by subscribers.
+ * + *
Multi-level wildcard
+ *
+ *

+ * The number sign (#) is a wildcard character that matches any number of + * levels within a topic. For example, if you subscribe to finance/stock/ibm/#, you receive messages + * on these topics: + *

+ *
    + *
  • finance/stock/ibm
  • + *
  • finance/stock/ibm/closingprice
  • + *
  • finance/stock/ibm/currentprice
  • + *
+ * + *

+ * The multi-level wildcard can represent zero or more levels. Therefore, + * finance/# can also match the singular finance, where + * # represents zero levels. The topic level separator is + * meaningless in this context, because there are no levels to separate. + *

+ * + *

+ * The multi-level wildcard can be specified only on its own or + * next to the topic level separator character. Therefore, # and + * finance/# are both valid, but finance# is not valid. + * The multi-level wildcard must be the last character used within the + * topic tree. For example, finance/# is valid but + * finance/#/closingprice is not valid. + *

+ *
+ * + *
Single-level wildcard
+ *
+ *

+ * The plus sign (+) is a wildcard character that matches only one topic + * level. For example, finance/stock/+ matches + * finance/stock/ibm and finance/stock/xyz, but not + * finance/stock/ibm/closingprice. Also, because the single-level + * wildcard matches only a single level, finance/+ does not match + * finance. + *

+ * + *

+ * Use the single-level wildcard at any level in the topic tree, and in + * conjunction with the multilevel wildcard. Specify the single-level + * wildcard next to the topic level separator, except when it is specified + * on its own. Therefore, + and finance/+ are both valid, + * but finance+ is not valid. The single-level wildcard can + * be used at the end of the topic tree or within the topic tree. For + * example, finance/+ and finance/+/ibm are both + * valid. + *

+ *
+ *
+ *

+ * The method returns control before the subscribe completes. Completion can + * be tracked by: + *

+ *
    + *
  • Waiting on the supplied token {@link MqttToken#waitForCompletion()} + * or
  • + *
  • Passing in a callback {@link IMqttActionListener} to this method
  • + *
+ * + * @param topic + * one or more topics to subscribe to, which can include + * wildcards + * @param qos + * the maximum quality of service to subscribe each topic + * at.Messages published at a lower quality of service will be + * received at the published QoS. Messages published at a higher + * quality of service will be received using the QoS specified on + * the subscription. + * @param userContext + * optional object used to pass context to the callback. Use null + * if not required. + * @param callback + * optional listener that will be notified when subscribe has + * completed + * @return token used to track and wait for the subscribe to complete. The + * token will be passed to callback methods if set. + * @throws MqttException + * if there was an error registering the subscription. + * @throws IllegalArgumentException + * if the two supplied arrays are not the same size. + */ + @Override + public IMqttToken subscribe(String[] topic, int[] qos, Object userContext, + IMqttActionListener callback) throws MqttException { + IMqttToken token = new MqttTokenAndroid(this, userContext, + callback, topic); + String activityToken = storeToken(token); + mqttService.subscribe(clientHandle, topic, qos, null, activityToken); + return token; + } + + /** + * Subscribe to a topic, which may include wildcards. + * + * @see #subscribe(String[], int[], Object, IMqttActionListener) + * + * @param topicFilter the topic to subscribe to, which can include wildcards. + * @param qos the maximum quality of service at which to subscribe. Messages + * published at a lower quality of service will be received at the published + * QoS. Messages published at a higher quality of service will be received using + * the QoS specified on the subscribe. + * @param userContext optional object used to pass context to the callback. Use + * null if not required. + * @param callback optional listener that will be notified when subscribe + * has completed + * @param messageListener a callback to handle incoming messages + * @return token used to track and wait for the subscribe to complete. The token + * will be passed to callback methods if set. + * @throws MqttException if there was an error registering the subscription. + */ + public IMqttToken subscribe(String topicFilter, int qos, Object userContext, IMqttActionListener callback, IMqttMessageListener messageListener) throws MqttException { + + return subscribe(new String[] {topicFilter}, new int[] {qos}, userContext, callback, new IMqttMessageListener[] {messageListener}); + } + + /** + * Subscribe to a topic, which may include wildcards. + * + * @see #subscribe(String[], int[], Object, IMqttActionListener) + * + * @param topicFilter the topic to subscribe to, which can include wildcards. + * @param qos the maximum quality of service at which to subscribe. Messages + * published at a lower quality of service will be received at the published + * QoS. Messages published at a higher quality of service will be received using + * the QoS specified on the subscribe. + * @param messageListener a callback to handle incoming messages + * @return token used to track and wait for the subscribe to complete. The token + * will be passed to callback methods if set. + * @throws MqttException if there was an error registering the subscription. + */ + public IMqttToken subscribe(String topicFilter, int qos, IMqttMessageListener messageListener) throws MqttException { + + return subscribe(topicFilter, qos, null, null, messageListener); + } + + + /** + * Subscribe to multiple topics, each of which may include wildcards. + * + *

Provides an optimized way to subscribe to multiple topics compared to + * subscribing to each one individually.

+ * + * @see #subscribe(String[], int[], Object, IMqttActionListener) + * + * @param topicFilters one or more topics to subscribe to, which can include wildcards + * @param qos the maximum quality of service at which to subscribe. Messages + * published at a lower quality of service will be received at the published + * QoS. Messages published at a higher quality of service will be received using + * the QoS specified on the subscribe. + * @param messageListeners an array of callbacks to handle incoming messages + * @return token used to track and wait for the subscribe to complete. The token + * will be passed to callback methods if set. + * @throws MqttException if there was an error registering the subscription. + */ + public IMqttToken subscribe(String[] topicFilters, int[] qos, IMqttMessageListener[] messageListeners) throws MqttException { + + return subscribe(topicFilters, qos, null, null, messageListeners); + } + + + /** + * Subscribe to multiple topics, each of which may include wildcards. + * + *

Provides an optimized way to subscribe to multiple topics compared to + * subscribing to each one individually.

+ * + * @see #subscribe(String[], int[], Object, IMqttActionListener) + * + * @param topicFilters one or more topics to subscribe to, which can include wildcards + * @param qos the maximum quality of service at which to subscribe. Messages + * published at a lower quality of service will be received at the published + * QoS. Messages published at a higher quality of service will be received using + * the QoS specified on the subscribe. + * @param userContext optional object used to pass context to the callback. Use + * null if not required. + * @param callback optional listener that will be notified when subscribe + * has completed + * @param messageListeners an array of callbacks to handle incoming messages + * @return token used to track and wait for the subscribe to complete. The token + * will be passed to callback methods if set. + * @throws MqttException if there was an error registering the subscription. + */ + public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback, IMqttMessageListener[] messageListeners) throws MqttException { + IMqttToken token = new MqttTokenAndroid(this, userContext, callback, topicFilters); + String activityToken = storeToken(token); + mqttService.subscribe(clientHandle, topicFilters, qos, null, activityToken, messageListeners); + + return null; + } + + + /** + * Requests the server unsubscribe the client from a topic. + * + * @param topic + * the topic to unsubscribe from. It must match a topic specified + * on an earlier subscribe. + * @return token used to track and wait for the unsubscribe to complete. The + * token will be passed to callback methods if set. + * @throws MqttException + * if there was an error unregistering the subscription. + * + * @see #unsubscribe(String[], Object, IMqttActionListener) + */ + @Override + public IMqttToken unsubscribe(String topic) throws MqttException { + return unsubscribe(topic, null, null); + } + + /** + * Requests the server to unsubscribe the client from one or more topics. + * + * @param topic + * one or more topics to unsubscribe from. Each topic must match + * one specified on an earlier subscription. + * @return token used to track and wait for the unsubscribe to complete. The + * token will be passed to callback methods if set. + * @throws MqttException + * if there was an error unregistering the subscription. + * + * @see #unsubscribe(String[], Object, IMqttActionListener) + */ + @Override + public IMqttToken unsubscribe(String[] topic) throws MqttException { + return unsubscribe(topic, null, null); + } + + /** + * Requests the server to unsubscribe the client from a topics. + * + * @param topic + * the topic to unsubscribe from. It must match a topic specified + * on an earlier subscribe. + * @param userContext + * optional object used to pass context to the callback. Use null + * if not required. + * @param callback + * optional listener that will be notified when unsubscribe has + * completed + * @return token used to track and wait for the unsubscribe to complete. The + * token will be passed to callback methods if set. + * @throws MqttException + * if there was an error unregistering the subscription. + * + * @see #unsubscribe(String[], Object, IMqttActionListener) + */ + @Override + public IMqttToken unsubscribe(String topic, Object userContext, + IMqttActionListener callback) throws MqttException { + IMqttToken token = new MqttTokenAndroid(this, userContext, + callback); + String activityToken = storeToken(token); + mqttService.unsubscribe(clientHandle, topic, null, activityToken); + return token; + } + + /** + * Requests the server to unsubscribe the client from one or more topics. + *

+ * Unsubcribing is the opposite of subscribing. When the server receives the + * unsubscribe request it looks to see if it can find a matching + * subscription for the client and then removes it. After this point the + * server will send no more messages to the client for this subscription. + *

+ *

+ * The topic(s) specified on the unsubscribe must match the topic(s) + * specified in the original subscribe request for the unsubscribe to + * succeed + *

+ *

+ * The method returns control before the unsubscribe completes. Completion + * can be tracked by: + *

+ *
    + *
  • Waiting on the returned token {@link MqttToken#waitForCompletion()} + * or
  • + *
  • Passing in a callback {@link IMqttActionListener} to this method
  • + *
+ * + * @param topic + * one or more topics to unsubscribe from. Each topic must match + * one specified on an earlier subscription. + * @param userContext + * optional object used to pass context to the callback. Use null + * if not required. + * @param callback + * optional listener that will be notified when unsubscribe has + * completed + * @return token used to track and wait for the unsubscribe to complete. The + * token will be passed to callback methods if set. + * @throws MqttException + * if there was an error unregistering the subscription. + */ + @Override + public IMqttToken unsubscribe(String[] topic, Object userContext, + IMqttActionListener callback) throws MqttException { + IMqttToken token = new MqttTokenAndroid(this, userContext, + callback); + String activityToken = storeToken(token); + mqttService.unsubscribe(clientHandle, topic, null, activityToken); + return token; + } + + /** + * Returns the delivery tokens for any outstanding publish operations. + *

+ * If a client has been restarted and there are messages that were in the + * process of being delivered when the client stopped, this method returns a + * token for each in-flight message to enable the delivery to be tracked. + * Alternately the {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)} + * callback can be used to track the delivery of outstanding messages. + *

+ *

+ * If a client connects with cleanSession true then there will be no + * delivery tokens as the cleanSession option deletes all earlier state. For + * state to be remembered the client must connect with cleanSession set to + * false + *

+ * + * @return zero or more delivery tokens + */ + @Override + public IMqttDeliveryToken[] getPendingDeliveryTokens() { + return mqttService.getPendingDeliveryTokens(clientHandle); + } + + /** + * Sets a callback listener to use for events that happen asynchronously. + *

+ * There are a number of events that the listener will be notified about. + * These include: + *

+ *
    + *
  • A new message has arrived and is ready to be processed
  • + *
  • The connection to the server has been lost
  • + *
  • Delivery of a message to the server has completed
  • + *
+ *

+ * Other events that track the progress of an individual operation such as + * connect and subscribe can be tracked using the {@link MqttToken} returned + * from each non-blocking method or using setting a + * {@link IMqttActionListener} on the non-blocking method. + *

+ * + * @param callback + * which will be invoked for certain asynchronous events + * + * @see MqttCallback + */ + @Override + public void setCallback(MqttCallback callback) { + this.callback = callback; + + } + + /** + * identify the callback to be invoked when making tracing calls back into + * the Activity + * + * @param traceCallback handler + */ + public void setTraceCallback(MqttTraceHandler traceCallback) { + this.traceCallback = traceCallback; + // mqttService.setTraceCallbackId(traceCallbackId); + } + + /** + * turn tracing on and off + * + * @param traceEnabled + * set true to enable trace, otherwise, set + * false to disable trace + * + */ + public void setTraceEnabled(boolean traceEnabled) { + this.traceEnabled = traceEnabled; + if (mqttService !=null) + mqttService.setTraceEnabled(traceEnabled); + } + + /** + *

+ * Process incoming Intent objects representing the results of operations + * and asynchronous activities such as message received + *

+ *

+ * Note: This is only a public method because the Android + * APIs require such.
+ * This method should not be explicitly invoked. + *

+ */ + @Override + public void onReceive(Context context, Intent intent) { + Bundle data = intent.getExtras(); + + String handleFromIntent = data + .getString(MqttServiceConstants.CALLBACK_CLIENT_HANDLE); + + if ((handleFromIntent == null) + || (!handleFromIntent.equals(clientHandle))) { + return; + } + + String action = data.getString(MqttServiceConstants.CALLBACK_ACTION); + + if (MqttServiceConstants.CONNECT_ACTION.equals(action)) { + connectAction(data); + } + else if (MqttServiceConstants.CONNECT_EXTENDED_ACTION.equals(action)){ + connectExtendedAction(data); + } + else if (MqttServiceConstants.MESSAGE_ARRIVED_ACTION.equals(action)) { + messageArrivedAction(data); + } + else if (MqttServiceConstants.SUBSCRIBE_ACTION.equals(action)) { + subscribeAction(data); + } + else if (MqttServiceConstants.UNSUBSCRIBE_ACTION.equals(action)) { + unSubscribeAction(data); + } + else if (MqttServiceConstants.SEND_ACTION.equals(action)) { + sendAction(data); + } + else if (MqttServiceConstants.MESSAGE_DELIVERED_ACTION.equals(action)) { + messageDeliveredAction(data); + } + else if (MqttServiceConstants.ON_CONNECTION_LOST_ACTION + .equals(action)) { + connectionLostAction(data); + } + else if (MqttServiceConstants.DISCONNECT_ACTION.equals(action)) { + disconnected(data); + } + else if (MqttServiceConstants.TRACE_ACTION.equals(action)) { + traceAction(data); + }else{ + mqttService.traceError(MqttService.TAG, "Callback action doesn't exist."); + } + + } + + /** + * Acknowledges a message received on the + * {@link MqttCallback#messageArrived(String, MqttMessage)} + * + * @param messageId + * the messageId received from the MqttMessage (To access this + * field you need to cast {@link MqttMessage} to + * {@link ParcelableMqttMessage}) + * @return whether or not the message was successfully acknowledged + */ + public boolean acknowledgeMessage(String messageId) { + if (messageAck == Ack.MANUAL_ACK) { + Status status = mqttService.acknowledgeMessageArrival(clientHandle, messageId); + return status == Status.OK; + } + return false; + + } + + public void messageArrivedComplete(int messageId, int qos) throws MqttException { + throw new UnsupportedOperationException(); + } + + public void setManualAcks(boolean manualAcks) { + throw new UnsupportedOperationException(); + } + + /** + * Process the results of a connection + * + * @param data + */ + private void connectAction(Bundle data) { + IMqttToken token = connectToken; + removeMqttToken(data); + + simpleAction(token, data); + } + + + + /** + * Process a notification that we have disconnected + * + * @param data + */ + private void disconnected(Bundle data) { + clientHandle = null; // avoid reuse! + IMqttToken token = removeMqttToken(data); + if (token != null) { + ((MqttTokenAndroid) token).notifyComplete(); + } + if (callback != null) { + callback.connectionLost(null); + } + } + + /** + * Process a Connection Lost notification + * + * @param data + */ + private void connectionLostAction(Bundle data) { + if (callback != null) { + Exception reason = (Exception) data + .getSerializable(MqttServiceConstants.CALLBACK_EXCEPTION); + callback.connectionLost(reason); + } + } + + private void connectExtendedAction(Bundle data){ + // This is called differently from a normal connect + + if(callback instanceof MqttCallbackExtended){ + boolean reconnect = data.getBoolean(MqttServiceConstants.CALLBACK_RECONNECT, false); + String serverURI = data.getString(MqttServiceConstants.CALLBACK_SERVER_URI); + ((MqttCallbackExtended) callback).connectComplete(reconnect, serverURI); + } + + } + + /** + * Common processing for many notifications + * + * @param token + * the token associated with the action being undertake + * @param data + * the result data + */ + private void simpleAction(IMqttToken token, Bundle data) { + if (token != null) { + Status status = (Status) data + .getSerializable(MqttServiceConstants.CALLBACK_STATUS); + if (status == Status.OK) { + ((MqttTokenAndroid) token).notifyComplete(); + } + else { + Exception exceptionThrown = (Exception) data.getSerializable(MqttServiceConstants.CALLBACK_EXCEPTION); + ((MqttTokenAndroid) token).notifyFailure(exceptionThrown); + } + } else { + mqttService.traceError(MqttService.TAG, "simpleAction : token is null"); + } + } + + /** + * Process notification of a publish(send) operation + * + * @param data + */ + private void sendAction(Bundle data) { + IMqttToken token = getMqttToken(data); // get, don't remove - will + // remove on delivery + simpleAction(token, data); + } + + /** + * Process notification of a subscribe operation + * + * @param data + */ + private void subscribeAction(Bundle data) { + IMqttToken token = removeMqttToken(data); + simpleAction(token, data); + } + + /** + * Process notification of an unsubscribe operation + * + * @param data + */ + private void unSubscribeAction(Bundle data) { + IMqttToken token = removeMqttToken(data); + simpleAction(token, data); + } + + /** + * Process notification of a published message having been delivered + * + * @param data + */ + private void messageDeliveredAction(Bundle data) { + IMqttToken token = removeMqttToken(data); + if (token != null) { + if (callback != null) { + Status status = (Status) data + .getSerializable(MqttServiceConstants.CALLBACK_STATUS); + if (status == Status.OK && token instanceof IMqttDeliveryToken) { + callback.deliveryComplete((IMqttDeliveryToken) token); + } + } + } + } + + /** + * Process notification of a message's arrival + * + * @param data + */ + private void messageArrivedAction(Bundle data) { + if (callback != null) { + String messageId = data + .getString(MqttServiceConstants.CALLBACK_MESSAGE_ID); + String destinationName = data + .getString(MqttServiceConstants.CALLBACK_DESTINATION_NAME); + + ParcelableMqttMessage message = data + .getParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL); + try { + if (messageAck == Ack.AUTO_ACK) { + callback.messageArrived(destinationName, message); + mqttService.acknowledgeMessageArrival(clientHandle, messageId); + } + else { + message.messageId = messageId; + callback.messageArrived(destinationName, message); + } + + // let the service discard the saved message details + } + catch (Exception e) { + // Swallow the exception + } + } + } + + /** + * Process trace action - pass trace data back to the callback + * + * @param data + */ + private void traceAction(Bundle data) { + + if (traceCallback != null) { + String severity = data.getString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY); + String message = data.getString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE); + String tag = data.getString(MqttServiceConstants.CALLBACK_TRACE_TAG); + if (MqttServiceConstants.TRACE_DEBUG.equals(severity)) + traceCallback.traceDebug(tag, message); + else if (MqttServiceConstants.TRACE_ERROR.equals(severity)) + traceCallback.traceError(tag, message); + else + { + Exception e = (Exception) data.getSerializable(MqttServiceConstants.CALLBACK_EXCEPTION); + traceCallback.traceException(tag, message, e); + } + } + } + + /** + * @param token + * identifying an operation + * @return an identifier for the token which can be passed to the Android + * Service + */ + private synchronized String storeToken(IMqttToken token) { + tokenMap.put(tokenNumber, token); + return Integer.toString(tokenNumber++); + } + + /** + * Get a token identified by a string, and remove it from our map + * + * @param data + * @return the token + */ + private synchronized IMqttToken removeMqttToken(Bundle data) { + + String activityToken = data.getString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN); + if (activityToken!=null){ + int tokenNumber = Integer.parseInt(activityToken); + IMqttToken token = tokenMap.get(tokenNumber); + tokenMap.delete(tokenNumber); + return token; + } + return null; + } + + /** + * Get a token identified by a string, and remove it from our map + * + * @param data + * @return the token + */ + private synchronized IMqttToken getMqttToken(Bundle data) { + String activityToken = data + .getString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN); + return tokenMap.get(Integer.parseInt(activityToken)); + } + + /** + * Sets the DisconnectedBufferOptions for this client + * @param bufferOpts the DisconnectedBufferOptions + */ + public void setBufferOpts(DisconnectedBufferOptions bufferOpts) { + mqttService.setBufferOpts(clientHandle, bufferOpts); + } + + public int getBufferedMessageCount(){ + return mqttService.getBufferedMessageCount(clientHandle); + } + + public MqttMessage getBufferedMessage(int bufferIndex){ + return mqttService.getBufferedMessage(clientHandle, bufferIndex); + } + + public void deleteBufferedMessage(int bufferIndex){ + mqttService.deleteBufferedMessage(clientHandle, bufferIndex); + } + + /** + * Get the SSLSocketFactory using SSL key store and password + *

+ * A convenience method, which will help user to create a SSLSocketFactory + * object + *

+ * + * @param keyStore + * the SSL key store which is generated by some SSL key tool, + * such as keytool in Java JDK + * @param password + * the password of the key store which is set when the key store + * is generated + * @return SSLSocketFactory used to connect to the server with SSL + * authentication + * @throws MqttSecurityException + * if there was any error when getting the SSLSocketFactory + */ + public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException { + try{ + SSLContext ctx = null; + SSLSocketFactory sslSockFactory=null; + KeyStore ts; + ts = KeyStore.getInstance("BKS"); + ts.load(keyStore, password.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); + tmf.init(ts); + TrustManager[] tm = tmf.getTrustManagers(); + ctx = SSLContext.getInstance("TLSv1"); + ctx.init(null, tm, null); + + sslSockFactory=ctx.getSocketFactory(); + return sslSockFactory; + + } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | KeyManagementException e) { + throw new MqttSecurityException(e); + } + } + + @Override + public void disconnectForcibly() throws MqttException { + throw new UnsupportedOperationException(); + } + + @Override + public void disconnectForcibly(long disconnectTimeout) throws MqttException { + throw new UnsupportedOperationException(); + } + + @Override + public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) + throws MqttException { + throw new UnsupportedOperationException(); + } + + /** + * Unregister receiver which receives intent from MqttService avoids + * IntentReceiver leaks. + */ + public void unregisterResources(){ + if(myContext != null && receiverRegistered){ + synchronized (MqttAndroidClient.this) { + LocalBroadcastManager.getInstance(myContext).unregisterReceiver(this); + receiverRegistered = false; + } + if(bindedService){ + try{ + myContext.unbindService(serviceConnection); + bindedService = false; + }catch(IllegalArgumentException e){ + //Ignore unbind issue. + } + } + } + } + + /** + * Register receiver to receiver intent from MqttService. Call this method + * when activity is hidden and become to show again. + * + * @param context + * - Current activity context. + */ + public void registerResources(Context context){ + if(context != null){ + this.myContext = context; + if(!receiverRegistered){ + registerReceiver(this); + } + } + } +} diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttConnection.java b/app/src/main/java/org/eclipse/paho/android/service/MqttConnection.java new file mode 100644 index 0000000..a744efe --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/MqttConnection.java @@ -0,0 +1,1146 @@ +/******************************************************************************* + * Copyright (c) 1999, 2016 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.paho.android.service; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.paho.android.service.MessageStore.StoredMessage; +import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.IMqttMessageListener; +import org.eclipse.paho.client.mqttv3.IMqttToken; +import org.eclipse.paho.client.mqttv3.MqttAsyncClient; +import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; +import org.eclipse.paho.client.mqttv3.MqttClientPersistence; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.MqttPersistenceException; +import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence; + +import android.app.Service; +import android.content.Context; +import android.os.Bundle; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.util.Log; + +/** + *

+ * MqttConnection holds a MqttAsyncClient {host,port,clientId} instance to perform + * MQTT operations to MQTT broker. + *

+ *

+ * Most of the major API here is intended to implement the most general forms of + * the methods in IMqttAsyncClient, with slight adjustments for the Android + * environment
+ * These adjustments usually consist of adding two parameters to each method :- + *

    + *
  • invocationContext - a string passed from the application to identify the + * context of the operation (mainly included for support of the javascript API + * implementation)
  • + *
  • activityToken - a string passed from the Activity to relate back to a + * callback method or other context-specific data
  • + *
+ *

+ *

+ * Operations are very much asynchronous, so success and failure are notified by + * packing the relevant data into Intent objects which are broadcast back to the + * Activity via the MqttService.callbackToActivity() method. + *

+ */ +class MqttConnection implements MqttCallbackExtended { + + // Strings for Intents etc.. + private static final String TAG = "MqttConnection"; + // Error status messages + private static final String NOT_CONNECTED = "not connected"; + + // fields for the connection definition + private String serverURI; + public String getServerURI() { + return serverURI; + } + + public void setServerURI(String serverURI) { + this.serverURI = serverURI; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + private String clientId; + private MqttClientPersistence persistence = null; + private MqttConnectOptions connectOptions; + + public MqttConnectOptions getConnectOptions() { + return connectOptions; + } + + public void setConnectOptions(MqttConnectOptions connectOptions) { + this.connectOptions = connectOptions; + } + + // Client handle, used for callbacks... + private String clientHandle; + + public String getClientHandle() { + return clientHandle; + } + + public void setClientHandle(String clientHandle) { + this.clientHandle = clientHandle; + } + + //store connect ActivityToken for reconnect + private String reconnectActivityToken = null; + + // our client object - instantiated on connect + private MqttAsyncClient myClient = null; + + //private AlarmPingSender alarmPingSender = null; + private RitoPingSender alarmPingSender = null; + + // our (parent) service object + private MqttService service = null; + + private volatile boolean disconnected = true; + private boolean cleanSession = true; + + // Indicate this connection is connecting or not. + // This variable uses to avoid reconnect multiple times. + private volatile boolean isConnecting = false; + + // Saved sent messages and their corresponding Topics, activityTokens and + // invocationContexts, so we can handle "deliveryComplete" callbacks + // from the mqttClient + private Map savedTopics = new HashMap<>(); + private Map savedSentMessages = new HashMap<>(); + private Map savedActivityTokens = new HashMap<>(); + private Map savedInvocationContexts = new HashMap<>(); + + private WakeLock wakelock = null; + private String wakeLockTag = null; + + private DisconnectedBufferOptions bufferOpts = null; + + /** + * Constructor - create an MqttConnection to communicate with MQTT server + * + * @param service + * our "parent" service - we make callbacks to it + * @param serverURI + * the URI of the MQTT server to which we will connect + * @param clientId + * the name by which we will identify ourselves to the MQTT + * server + * @param persistence + * the persistence class to use to store in-flight message. If + * null then the default persistence mechanism is used + * @param clientHandle + * the "handle" by which the activity will identify us + */ + MqttConnection(MqttService service, String serverURI, String clientId, + MqttClientPersistence persistence, String clientHandle) { + this.serverURI = serverURI; + this.service = service; + this.clientId = clientId; + this.persistence = persistence; + this.clientHandle = clientHandle; + + StringBuilder stringBuilder = new StringBuilder(this.getClass().getCanonicalName()); + stringBuilder.append(" "); + stringBuilder.append(clientId); + stringBuilder.append(" "); + stringBuilder.append("on host "); + stringBuilder.append(serverURI); + wakeLockTag = stringBuilder.toString(); + } + + // The major API implementation follows + /** + * Connect to the server specified when we were instantiated + * + * @param options + * timeout, etc + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + public void connect(MqttConnectOptions options, String invocationContext, + String activityToken) { + + connectOptions = options; + reconnectActivityToken = activityToken; + + if (options != null) { + cleanSession = options.isCleanSession(); + } + + if (connectOptions.isCleanSession()) { // if it's a clean session, + // discard old data + service.messageStore.clearArrivedMessages(clientHandle); + } + + service.traceDebug(TAG, "Connecting {" + serverURI + "} as {" + clientId + "}"); + final Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + activityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, + invocationContext); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.CONNECT_ACTION); + + + try { + if (persistence == null) { + // ask Android where we can put files + File myDir = service.getExternalFilesDir(TAG); + + if (myDir == null) { + // No external storage, use internal storage instead. + myDir = service.getDir(TAG, Context.MODE_PRIVATE); + + if(myDir == null){ + //Shouldn't happen. + resultBundle.putString( + MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + "Error! No external and internal storage available"); + resultBundle.putSerializable( + MqttServiceConstants.CALLBACK_EXCEPTION, new MqttPersistenceException()); + service.callbackToActivity(clientHandle, Status.ERROR, + resultBundle); + return; + } + } + + // use that to setup MQTT client persistence storage + persistence = new MqttDefaultFilePersistence( + myDir.getAbsolutePath()); + } + + IMqttActionListener listener = new MqttConnectionListener( + resultBundle) { + + @Override + public void onSuccess(IMqttToken asyncActionToken) { + doAfterConnectSuccess(resultBundle); + service.traceDebug(TAG, "connect success!"); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, + Throwable exception) { + resultBundle.putString( + MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + exception.getLocalizedMessage()); + resultBundle.putSerializable( + MqttServiceConstants.CALLBACK_EXCEPTION, exception); + service.traceError(TAG, + "connect fail, call connect to reconnect.reason:" + + exception.getMessage()); + + doAfterConnectFail(resultBundle); + + } + }; + + if (myClient != null) { + if (isConnecting ) { + service.traceDebug(TAG, + "myClient != null and the client is connecting. Connect return directly."); + service.traceDebug(TAG,"Connect return:isConnecting:"+isConnecting+".disconnected:"+disconnected); + }else if(!disconnected){ + service.traceDebug(TAG,"myClient != null and the client is connected and notify!"); + doAfterConnectSuccess(resultBundle); + } + else { + service.traceDebug(TAG, "myClient != null and the client is not connected"); + service.traceDebug(TAG,"Do Real connect!"); + setConnectingState(true); + myClient.connect(connectOptions, invocationContext, listener); + } + } + + // if myClient is null, then create a new connection + else { + //alarmPingSender = new AlarmPingSender(service); + alarmPingSender = new RitoPingSender(); + myClient = new MqttAsyncClient(serverURI, clientId, + persistence, alarmPingSender); + myClient.setCallback(this); + + service.traceDebug(TAG,"Do Real connect!"); + setConnectingState(true); + myClient.connect(connectOptions, invocationContext, listener); + } + } catch (Exception e) { + service.traceError(TAG, "Exception occurred attempting to connect: " + e.getMessage()); + setConnectingState(false); + handleException(resultBundle, e); + } + } + + private void doAfterConnectSuccess(final Bundle resultBundle) { + //since the device's cpu can go to sleep, acquire a wakelock and drop it later. + acquireWakeLock(); + service.callbackToActivity(clientHandle, Status.OK, resultBundle); + deliverBacklog(); + setConnectingState(false); + disconnected = false; + releaseWakeLock(); + } + + @Override + public void connectComplete(boolean reconnect, String serverURI) { + Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.CONNECT_EXTENDED_ACTION); + resultBundle.putBoolean(MqttServiceConstants.CALLBACK_RECONNECT, reconnect); + resultBundle.putString(MqttServiceConstants.CALLBACK_SERVER_URI, serverURI); + service.callbackToActivity(clientHandle, Status.OK, resultBundle); + } + + private void doAfterConnectFail(final Bundle resultBundle){ + // + acquireWakeLock(); + disconnected = true; + setConnectingState(false); + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + releaseWakeLock(); + } + + private void handleException(final Bundle resultBundle, Exception e) { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + e.getLocalizedMessage()); + + resultBundle.putSerializable(MqttServiceConstants.CALLBACK_EXCEPTION, e); + + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + + /** + * Attempt to deliver any outstanding messages we've received but which the + * application hasn't acknowledged. If "cleanSession" was specified, we'll + * have already purged any such messages from our messageStore. + */ + private void deliverBacklog() { + Iterator backlog = service.messageStore + .getAllArrivedMessages(clientHandle); + while (backlog.hasNext()) { + StoredMessage msgArrived = backlog.next(); + Bundle resultBundle = messageToBundle(msgArrived.getMessageId(), + msgArrived.getTopic(), msgArrived.getMessage()); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.MESSAGE_ARRIVED_ACTION); + service.callbackToActivity(clientHandle, Status.OK, resultBundle); + } + } + + /** + * Create a bundle containing all relevant data pertaining to a message + * + * @param messageId + * the message's identifier in the messageStore, so that a + * callback can be made to remove it once delivered + * @param topic + * the topic on which the message was delivered + * @param message + * the message itself + * @return the bundle + */ + private Bundle messageToBundle(String messageId, String topic, + MqttMessage message) { + Bundle result = new Bundle(); + result.putString(MqttServiceConstants.CALLBACK_MESSAGE_ID, messageId); + result.putString(MqttServiceConstants.CALLBACK_DESTINATION_NAME, topic); + result.putParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL, + new ParcelableMqttMessage(message)); + return result; + } + + /** + * Close connection from the server + * + */ + void close() { + service.traceDebug(TAG, "close()"); + try { + if (myClient != null) { + myClient.close(); + } + } catch (MqttException e) { + // Pass a new bundle, let handleException stores error messages. + handleException(new Bundle(), e); + } + } + + /** + * Disconnect from the server + * + * @param quiesceTimeout + * in milliseconds + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary string to be passed back to the activity + */ + void disconnect(long quiesceTimeout, String invocationContext, + String activityToken) { + service.traceDebug(TAG, "disconnect()"); + disconnected = true; + final Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + activityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, + invocationContext); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.DISCONNECT_ACTION); + if ((myClient != null) && (myClient.isConnected())) { + IMqttActionListener listener = new MqttConnectionListener( + resultBundle); + try { + myClient.disconnect(quiesceTimeout, invocationContext, listener); + } catch (Exception e) { + handleException(resultBundle, e); + } + } else { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + NOT_CONNECTED); + service.traceError(MqttServiceConstants.DISCONNECT_ACTION, + NOT_CONNECTED); + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + + if (connectOptions != null && connectOptions.isCleanSession()) { + // assume we'll clear the stored messages at this point + service.messageStore.clearArrivedMessages(clientHandle); + } + + releaseWakeLock(); + } + + /** + * Disconnect from the server + * + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary string to be passed back to the activity + */ + void disconnect(String invocationContext, String activityToken) { + service.traceDebug(TAG, "disconnect()"); + disconnected = true; + final Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + activityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, + invocationContext); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.DISCONNECT_ACTION); + if ((myClient != null) && (myClient.isConnected())) { + IMqttActionListener listener = new MqttConnectionListener( + resultBundle); + try { + myClient.disconnect(invocationContext, listener); + } catch (Exception e) { + handleException(resultBundle, e); + } + } else { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + NOT_CONNECTED); + service.traceError(MqttServiceConstants.DISCONNECT_ACTION, + NOT_CONNECTED); + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + + if (connectOptions != null && connectOptions.isCleanSession()) { + // assume we'll clear the stored messages at this point + service.messageStore.clearArrivedMessages(clientHandle); + } + releaseWakeLock(); + } + + /** + * @return true if we are connected to an MQTT server + */ + public boolean isConnected() { + return myClient != null && myClient.isConnected(); + } + + /** + * Publish a message on a topic + * + * @param topic + * the topic on which to publish - represented as a string, not + * an MqttTopic object + * @param payload + * the content of the message to publish + * @param qos + * the quality of service requested + * @param retained + * whether the MQTT server should retain this message + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary string to be passed back to the activity + * @return token for tracking the operation + */ + public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, + boolean retained, String invocationContext, String activityToken) { + final Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.SEND_ACTION); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + activityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, + invocationContext); + + IMqttDeliveryToken sendToken = null; + + if ((myClient != null) && (myClient.isConnected())) { + IMqttActionListener listener = new MqttConnectionListener( + resultBundle); + try { + MqttMessage message = new MqttMessage(payload); + message.setQos(qos); + message.setRetained(retained); + sendToken = myClient.publish(topic, payload, qos, retained, + invocationContext, listener); + storeSendDetails(topic, message, sendToken, invocationContext, + activityToken); + } catch (Exception e) { + handleException(resultBundle, e); + } + } else { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + NOT_CONNECTED); + service.traceError(MqttServiceConstants.SEND_ACTION, NOT_CONNECTED); + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + + return sendToken; + } + + /** + * Publish a message on a topic + * + * @param topic + * the topic on which to publish - represented as a string, not + * an MqttTopic object + * @param message + * the message to publish + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary string to be passed back to the activity + * @return token for tracking the operation + */ + public IMqttDeliveryToken publish(String topic, MqttMessage message, + String invocationContext, String activityToken) { + final Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.SEND_ACTION); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + activityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, + invocationContext); + + IMqttDeliveryToken sendToken = null; + + if ((myClient != null) && (myClient.isConnected())) { + IMqttActionListener listener = new MqttConnectionListener( + resultBundle); + try { + sendToken = myClient.publish(topic, message, invocationContext, + listener); + storeSendDetails(topic, message, sendToken, invocationContext, + activityToken); + } catch (Exception e) { + handleException(resultBundle, e); + } + } else if ((myClient !=null) && (this.bufferOpts != null) && (this.bufferOpts.isBufferEnabled())){ + // Client is not connected, but buffer is enabled, so sending message + IMqttActionListener listener = new MqttConnectionListener( + resultBundle); + try { + sendToken = myClient.publish(topic, message, invocationContext, + listener); + storeSendDetails(topic, message, sendToken, invocationContext, + activityToken); + } catch (Exception e) { + handleException(resultBundle, e); + } + } else { + Log.i(TAG, "Client is not connected, so not sending message"); + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + NOT_CONNECTED); + service.traceError(MqttServiceConstants.SEND_ACTION, NOT_CONNECTED); + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + return sendToken; + } + + /** + * Subscribe to a topic + * + * @param topic + * a possibly wildcarded topic name + * @param qos + * requested quality of service for the topic + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + public void subscribe(final String topic, final int qos, + String invocationContext, String activityToken) { + service.traceDebug(TAG, "subscribe({" + topic + "}," + qos + ",{" + + invocationContext + "}, {" + activityToken + "}"); + final Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.SUBSCRIBE_ACTION); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + activityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, + invocationContext); + + if ((myClient != null) && (myClient.isConnected())) { + IMqttActionListener listener = new MqttConnectionListener( + resultBundle); + try { + myClient.subscribe(topic, qos, invocationContext, listener); + } catch (Exception e) { + handleException(resultBundle, e); + } + } else { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + NOT_CONNECTED); + service.traceError("subscribe", NOT_CONNECTED); + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + } + + /** + * Subscribe to one or more topics + * + * @param topic + * a list of possibly wildcarded topic names + * @param qos + * requested quality of service for each topic + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + public void subscribe(final String[] topic, final int[] qos, + String invocationContext, String activityToken) { + service.traceDebug(TAG, "subscribe({" + Arrays.toString(topic) + "}," + Arrays.toString(qos) + ",{" + + invocationContext + "}, {" + activityToken + "}"); + final Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.SUBSCRIBE_ACTION); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + activityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, + invocationContext); + + if ((myClient != null) && (myClient.isConnected())) { + IMqttActionListener listener = new MqttConnectionListener( + resultBundle); + try { + myClient.subscribe(topic, qos, invocationContext, listener); + } catch (Exception e) { + handleException(resultBundle, e); + } + } else { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + NOT_CONNECTED); + service.traceError("subscribe", NOT_CONNECTED); + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + } + + public void subscribe(String[] topicFilters, int[] qos, String invocationContext, String activityToken, IMqttMessageListener[] messageListeners) { + service.traceDebug(TAG, "subscribe({" + Arrays.toString(topicFilters) + "}," + Arrays.toString(qos) + ",{" + + invocationContext + "}, {" + activityToken + "}"); + final Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.SUBSCRIBE_ACTION); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, activityToken); + resultBundle.putString(MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, invocationContext); + if((myClient != null) && (myClient.isConnected())){ + IMqttActionListener listener = new MqttConnectionListener(resultBundle); + try { + + myClient.subscribe(topicFilters, qos,messageListeners); + } catch (Exception e){ + handleException(resultBundle, e); + } + } else { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, NOT_CONNECTED); + service.traceError("subscribe", NOT_CONNECTED); + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + } + + /** + * Unsubscribe from a topic + * + * @param topic + * a possibly wildcarded topic name + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + void unsubscribe(final String topic, String invocationContext, + String activityToken) { + service.traceDebug(TAG, "unsubscribe({" + topic + "},{" + + invocationContext + "}, {" + activityToken + "})"); + final Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.UNSUBSCRIBE_ACTION); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + activityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, + invocationContext); + if ((myClient != null) && (myClient.isConnected())) { + IMqttActionListener listener = new MqttConnectionListener( + resultBundle); + try { + myClient.unsubscribe(topic, invocationContext, listener); + } catch (Exception e) { + handleException(resultBundle, e); + } + } else { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + NOT_CONNECTED); + + service.traceError("subscribe", NOT_CONNECTED); + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + } + + /** + * Unsubscribe from one or more topics + * + * @param topic + * a list of possibly wildcarded topic names + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + void unsubscribe(final String[] topic, String invocationContext, + String activityToken) { + service.traceDebug(TAG, "unsubscribe({" + Arrays.toString(topic) + "},{" + + invocationContext + "}, {" + activityToken + "})"); + final Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.UNSUBSCRIBE_ACTION); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + activityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, + invocationContext); + if ((myClient != null) && (myClient.isConnected())) { + IMqttActionListener listener = new MqttConnectionListener( + resultBundle); + try { + myClient.unsubscribe(topic, invocationContext, listener); + } catch (Exception e) { + handleException(resultBundle, e); + } + } else { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + NOT_CONNECTED); + + service.traceError("subscribe", NOT_CONNECTED); + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + } + + /** + * Get tokens for all outstanding deliveries for a client + * + * @return an array (possibly empty) of tokens + */ + public IMqttDeliveryToken[] getPendingDeliveryTokens() { + return myClient.getPendingDeliveryTokens(); + } + + // Implement MqttCallback + /** + * Callback for connectionLost + * + * @param why + * the exeception causing the break in communications + */ + @Override + public void connectionLost(Throwable why) { + service.traceDebug(TAG, "connectionLost(" + why.getMessage() + ")"); + disconnected = true; + try { + if(!this.connectOptions.isAutomaticReconnect()) { + myClient.disconnect(null, new IMqttActionListener() { + + @Override + public void onSuccess(IMqttToken asyncActionToken) { + // No action + } + + @Override + public void onFailure(IMqttToken asyncActionToken, + Throwable exception) { + // No action + } + }); + } else { + // Using the new Automatic reconnect functionality. + // We can't force a disconnection, but we can speed one up + alarmPingSender.schedule(100); + + } + } catch (Exception e) { + // ignore it - we've done our best + } + + Bundle resultBundle = new Bundle(); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.ON_CONNECTION_LOST_ACTION); + if (why != null) { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + why.getMessage()); + if (why instanceof MqttException) { + resultBundle.putSerializable( + MqttServiceConstants.CALLBACK_EXCEPTION, why); + } + resultBundle.putString( + MqttServiceConstants.CALLBACK_EXCEPTION_STACK, + Log.getStackTraceString(why)); + } + service.callbackToActivity(clientHandle, Status.OK, resultBundle); + // client has lost connection no need for wake lock + releaseWakeLock(); + } + + /** + * Callback to indicate a message has been delivered (the exact meaning of + * "has been delivered" is dependent on the QOS value) + * + * @param messageToken + * the messge token provided when the message was originally sent + */ + @Override + public void deliveryComplete(IMqttDeliveryToken messageToken) { + + service.traceDebug(TAG, "deliveryComplete(" + messageToken + ")"); + + MqttMessage message = savedSentMessages.remove(messageToken); + if (message != null) { // If I don't know about the message, it's + // irrelevant + String topic = savedTopics.remove(messageToken); + String activityToken = savedActivityTokens.remove(messageToken); + String invocationContext = savedInvocationContexts + .remove(messageToken); + + Bundle resultBundle = messageToBundle(null, topic, message); + if (activityToken != null) { + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.SEND_ACTION); + resultBundle.putString( + MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + activityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, + invocationContext); + + service.callbackToActivity(clientHandle, Status.OK, + resultBundle); + } + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.MESSAGE_DELIVERED_ACTION); + service.callbackToActivity(clientHandle, Status.OK, resultBundle); + } + + // this notification will have kept the connection alive but send the previously sechudled ping anyway + } + + /** + * Callback when a message is received + * + * @param topic + * the topic on which the message was received + * @param message + * the message itself + */ + @Override + public void messageArrived(String topic, MqttMessage message) + throws Exception { + + service.traceDebug(TAG, + "messageArrived(" + topic + ",{" + message.toString() + "})"); + + String messageId = service.messageStore.storeArrived(clientHandle, + topic, message); + + Bundle resultBundle = messageToBundle(messageId, topic, message); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.MESSAGE_ARRIVED_ACTION); + resultBundle.putString(MqttServiceConstants.CALLBACK_MESSAGE_ID, + messageId); + service.callbackToActivity(clientHandle, Status.OK, resultBundle); + + } + + + + /** + * Store details of sent messages so we can handle "deliveryComplete" + * callbacks from the mqttClient + * + * @param topic + * @param msg + * @param messageToken + * @param invocationContext + * @param activityToken + */ + private void storeSendDetails(final String topic, final MqttMessage msg, + final IMqttDeliveryToken messageToken, + final String invocationContext, final String activityToken) { + savedTopics.put(messageToken, topic); + savedSentMessages.put(messageToken, msg); + savedActivityTokens.put(messageToken, activityToken); + savedInvocationContexts.put(messageToken, invocationContext); + } + + /** + * Acquires a partial wake lock for this client + */ + private void acquireWakeLock() { + if (wakelock == null) { + PowerManager pm = (PowerManager) service + .getSystemService(Service.POWER_SERVICE); + wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + wakeLockTag); + } + wakelock.acquire(); + + } + + /** + * Releases the currently held wake lock for this client + */ + private void releaseWakeLock() { + if(wakelock != null && wakelock.isHeld()){ + wakelock.release(); + } + } + + + + /** + * General-purpose IMqttActionListener for the Client context + *

+ * Simply handles the basic success/failure cases for operations which don't + * return results + * + */ + private class MqttConnectionListener implements IMqttActionListener { + + private final Bundle resultBundle; + + private MqttConnectionListener(Bundle resultBundle) { + this.resultBundle = resultBundle; + } + + @Override + public void onSuccess(IMqttToken asyncActionToken) { + service.callbackToActivity(clientHandle, Status.OK, resultBundle); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + exception.getLocalizedMessage()); + + resultBundle.putSerializable( + MqttServiceConstants.CALLBACK_EXCEPTION, exception); + + service.callbackToActivity(clientHandle, Status.ERROR, resultBundle); + } + } + + /** + * Receive notification that we are offline
+ * if cleanSession is true, we need to regard this as a disconnection + */ + void offline() { + + if (!disconnected && !cleanSession) { + Exception e = new Exception("Android offline"); + connectionLost(e); + } + } + + /** + * Reconnect
+ * Only appropriate if cleanSession is false and we were connected. + * Declare as synchronized to avoid multiple calls to this method to send connect + * multiple times + */ + synchronized void reconnect() { + + if (myClient == null) { + service.traceError(TAG,"Reconnect myClient = null. Will not do reconnect"); + return; + } + + if (isConnecting) { + service.traceDebug(TAG, "The client is connecting. Reconnect return directly."); + return ; + } + + if(!service.isOnline()){ + service.traceDebug(TAG, + "The network is not reachable. Will not do reconnect"); + return; + } + + if(connectOptions.isAutomaticReconnect()){ + //The Automatic reconnect functionality is enabled here + Log.i(TAG, "Requesting Automatic reconnect using New Java AC"); + final Bundle resultBundle = new Bundle(); + resultBundle.putString( + MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + reconnectActivityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, null); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.CONNECT_ACTION); + try { + myClient.reconnect(); + } catch (MqttException ex){ + Log.e(TAG, "Exception occurred attempting to reconnect: " + ex.getMessage()); + setConnectingState(false); + handleException(resultBundle, ex); + } + } else if (disconnected && !cleanSession) { + // use the activityToke the same with action connect + service.traceDebug(TAG,"Do Real Reconnect!"); + final Bundle resultBundle = new Bundle(); + resultBundle.putString( + MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN, + reconnectActivityToken); + resultBundle.putString( + MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, null); + resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION, + MqttServiceConstants.CONNECT_ACTION); + + try { + + IMqttActionListener listener = new MqttConnectionListener(resultBundle) { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + // since the device's cpu can go to sleep, acquire a + // wakelock and drop it later. + service.traceDebug(TAG,"Reconnect Success!"); + service.traceDebug(TAG,"DeliverBacklog when reconnect."); + doAfterConnectSuccess(resultBundle); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + resultBundle.putString( + MqttServiceConstants.CALLBACK_ERROR_MESSAGE, + exception.getLocalizedMessage()); + resultBundle.putSerializable( + MqttServiceConstants.CALLBACK_EXCEPTION, + exception); + service.callbackToActivity(clientHandle, Status.ERROR, + resultBundle); + + doAfterConnectFail(resultBundle); + + } + }; + + myClient.connect(connectOptions, null, listener); + setConnectingState(true); + } catch (MqttException e) { + service.traceError(TAG, "Cannot reconnect to remote server." + e.getMessage()); + setConnectingState(false); + handleException(resultBundle, e); + } catch (Exception e){ + /* TODO: Added Due to: https://github.com/eclipse/paho.mqtt.android/issues/101 + For some reason in a small number of cases, myClient is null here and so + a NullPointer Exception is thrown. This is a workaround to pass the exception + up to the application. myClient should not be null so more investigation is + required. + */ + service.traceError(TAG, "Cannot reconnect to remote server." + e.getMessage()); + setConnectingState(false); + MqttException newEx = new MqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR, e.getCause()); + handleException(resultBundle, newEx); + } + } + } + + /** + * + * @param isConnecting + */ + private synchronized void setConnectingState(boolean isConnecting){ + this.isConnecting = isConnecting; + } + + /** + * Sets the DisconnectedBufferOptions for this client + * @param bufferOpts + */ + public void setBufferOpts(DisconnectedBufferOptions bufferOpts) { + this.bufferOpts = bufferOpts; + myClient.setBufferOpts(bufferOpts); + } + + public int getBufferedMessageCount(){ + return myClient.getBufferedMessageCount(); + } + + public MqttMessage getBufferedMessage(int bufferIndex){ + return myClient.getBufferedMessage(bufferIndex); + } + + public void deleteBufferedMessage(int bufferIndex){ + myClient.deleteBufferedMessage(bufferIndex); + } +} diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttDeliveryTokenAndroid.java b/app/src/main/java/org/eclipse/paho/android/service/MqttDeliveryTokenAndroid.java new file mode 100644 index 0000000..12ae877 --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/MqttDeliveryTokenAndroid.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 1999, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.paho.android.service; + +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +/** + *

+ * Implementation of the IMqttDeliveryToken interface for use from within the + * MqttAndroidClient implementation + */ +class MqttDeliveryTokenAndroid extends MqttTokenAndroid + implements IMqttDeliveryToken { + + // The message which is being tracked by this token + private MqttMessage message; + + MqttDeliveryTokenAndroid(MqttAndroidClient client, + Object userContext, IMqttActionListener listener, MqttMessage message) { + super(client, userContext, listener); + this.message = message; + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttDeliveryToken#getMessage() + */ + @Override + public MqttMessage getMessage() throws MqttException { + return message; + } + + void setMessage(MqttMessage message) { + this.message = message; + } + + void notifyDelivery(MqttMessage delivered) { + message = delivered; + super.notifyComplete(); + } + +} diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttService.java b/app/src/main/java/org/eclipse/paho/android/service/MqttService.java new file mode 100644 index 0000000..3c902ed --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/MqttService.java @@ -0,0 +1,912 @@ +/******************************************************************************* + * Copyright (c) 1999, 2016 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * James Sutton - isOnline Null Pointer (bug 473775) + */ +package org.eclipse.paho.android.service; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.IMqttMessageListener; +import org.eclipse.paho.client.mqttv3.MqttClientPersistence; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.MqttPersistenceException; +import org.eclipse.paho.client.mqttv3.MqttSecurityException; + +import android.annotation.SuppressLint; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +/** + *

+ * The android service which interfaces with an MQTT client implementation + *

+ *

+ * The main API of MqttService is intended to pretty much mirror the + * IMqttAsyncClient with appropriate adjustments for the Android environment.
+ * These adjustments usually consist of adding two parameters to each method :- + *

+ *
    + *
  • invocationContext - a string passed from the application to identify the + * context of the operation (mainly included for support of the javascript API + * implementation)
  • + *
  • activityToken - a string passed from the Activity to relate back to a + * callback method or other context-specific data
  • + *
+ *

+ * To support multiple client connections, the bulk of the MQTT work is + * delegated to MqttConnection objects. These are identified by "client + * handle" strings, which is how the Activity, and the higher-level APIs refer + * to them. + *

+ *

+ * Activities using this service are expected to start it and bind to it using + * the BIND_AUTO_CREATE flag. The life cycle of this service is based on this + * approach. + *

+ *

+ * Operations are highly asynchronous - in most cases results are returned to + * the Activity by broadcasting one (or occasionally more) appropriate Intents, + * which the Activity is expected to register a listener for.
+ * The Intents have an Action of + * {@link MqttServiceConstants#CALLBACK_TO_ACTIVITY + * MqttServiceConstants.CALLBACK_TO_ACTIVITY} which allows the Activity to + * register a listener with an appropriate IntentFilter.
+ * Further data is provided by "Extra Data" in the Intent, as follows :- + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameData TypeValueOperations used for
+ * {@link MqttServiceConstants#CALLBACK_CLIENT_HANDLE + * MqttServiceConstants.CALLBACK_CLIENT_HANDLE}StringThe clientHandle identifying the client which + * initiated this operationAll operations
{@link MqttServiceConstants#CALLBACK_STATUS + * MqttServiceConstants.CALLBACK_STATUS}SerializableAn {@link Status} value indicating success or + * otherwise of the operationAll operations
+ * {@link MqttServiceConstants#CALLBACK_ACTIVITY_TOKEN + * MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN}Stringthe activityToken passed into the operationAll operations
+ * {@link MqttServiceConstants#CALLBACK_INVOCATION_CONTEXT + * MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT}Stringthe invocationContext passed into the operation + * All operations
{@link MqttServiceConstants#CALLBACK_ACTION + * MqttServiceConstants.CALLBACK_ACTION}Stringone of + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
{@link MqttServiceConstants#SEND_ACTION + * MqttServiceConstants.SEND_ACTION}
+ * {@link MqttServiceConstants#UNSUBSCRIBE_ACTION + * MqttServiceConstants.UNSUBSCRIBE_ACTION}
{@link MqttServiceConstants#SUBSCRIBE_ACTION + * MqttServiceConstants.SUBSCRIBE_ACTION}
{@link MqttServiceConstants#DISCONNECT_ACTION + * MqttServiceConstants.DISCONNECT_ACTION}
{@link MqttServiceConstants#CONNECT_ACTION + * MqttServiceConstants.CONNECT_ACTION}
+ * {@link MqttServiceConstants#MESSAGE_ARRIVED_ACTION + * MqttServiceConstants.MESSAGE_ARRIVED_ACTION}
+ * {@link MqttServiceConstants#MESSAGE_DELIVERED_ACTION + * MqttServiceConstants.MESSAGE_DELIVERED_ACTION}
+ * {@link MqttServiceConstants#ON_CONNECTION_LOST_ACTION + * MqttServiceConstants.ON_CONNECTION_LOST_ACTION}
+ *
All operations
+ * {@link MqttServiceConstants#CALLBACK_ERROR_MESSAGE + * MqttServiceConstants.CALLBACK_ERROR_MESSAGE} + * StringA suitable error message (taken from the + * relevant exception where possible)All failing operations
+ * {@link MqttServiceConstants#CALLBACK_ERROR_NUMBER + * MqttServiceConstants.CALLBACK_ERROR_NUMBER} + * intA suitable error code (taken from the relevant + * exception where possible)All failing operations
+ * {@link MqttServiceConstants#CALLBACK_EXCEPTION_STACK + * MqttServiceConstants.CALLBACK_EXCEPTION_STACK}StringThe stacktrace of the failing callThe Connection Lost event
+ * {@link MqttServiceConstants#CALLBACK_MESSAGE_ID + * MqttServiceConstants.CALLBACK_MESSAGE_ID}StringThe identifier for the message in the message + * store, used by the Activity to acknowledge the arrival of the message, so + * that the service may remove it from the storeThe Message Arrived event
+ * {@link MqttServiceConstants#CALLBACK_DESTINATION_NAME + * MqttServiceConstants.CALLBACK_DESTINATION_NAME} + * StringThe topic on which the message was receivedThe Message Arrived event
+ * {@link MqttServiceConstants#CALLBACK_MESSAGE_PARCEL + * MqttServiceConstants.CALLBACK_MESSAGE_PARCEL}ParcelableThe new message encapsulated in Android + * Parcelable format as a {@link ParcelableMqttMessage}The Message Arrived event
+ */ +@SuppressLint("Registered") +public class MqttService extends Service implements MqttTraceHandler { + + // Identifier for Intents, log messages, etc.. + static final String TAG = "MqttService"; + + // callback id for making trace callbacks to the Activity + // needs to be set by the activity as appropriate + private String traceCallbackId; + // state of tracing + private boolean traceEnabled = false; + + // somewhere to persist received messages until we're sure + // that they've reached the application + MessageStore messageStore; + + // An intent receiver to deal with changes in network connectivity + private NetworkConnectionIntentReceiver networkConnectionMonitor; + + //a receiver to recognise when the user changes the "background data" preference + // and a flag to track that preference + // Only really relevant below android version ICE_CREAM_SANDWICH - see + // android docs + private BackgroundDataPreferenceReceiver backgroundDataPreferenceMonitor; + private volatile boolean backgroundDataEnabled = true; + + // a way to pass ourself back to the activity + private MqttServiceBinder mqttServiceBinder; + + // mapping from client handle strings to actual client connections. + private Map connections = new ConcurrentHashMap<>(); + + public MqttService() { + super(); + } + + /** + * pass data back to the Activity, by building a suitable Intent object and + * broadcasting it + * + * @param clientHandle + * source of the data + * @param status + * OK or Error + * @param dataBundle + * the data to be passed + */ + void callbackToActivity(String clientHandle, Status status, + Bundle dataBundle) { + // Don't call traceDebug, as it will try to callbackToActivity leading + // to recursion. + Intent callbackIntent = new Intent( + MqttServiceConstants.CALLBACK_TO_ACTIVITY); + if (clientHandle != null) { + callbackIntent.putExtra( + MqttServiceConstants.CALLBACK_CLIENT_HANDLE, clientHandle); + } + callbackIntent.putExtra(MqttServiceConstants.CALLBACK_STATUS, status); + if (dataBundle != null) { + callbackIntent.putExtras(dataBundle); + } + LocalBroadcastManager.getInstance(this).sendBroadcast(callbackIntent); + } + + // The major API implementation follows :- + + /** + * Get an MqttConnection object to represent a connection to a server + * + * @param serverURI specifies the protocol, host name and port to be used to connect to an MQTT server + * @param clientId specifies the name by which this connection should be identified to the server + * @param contextId specifies the app conext info to make a difference between apps + * @param persistence specifies the persistence layer to be used with this client + * @return a string to be used by the Activity as a "handle" for this + * MqttConnection + */ + public String getClient(String serverURI, String clientId, String contextId, MqttClientPersistence persistence) { + String clientHandle = serverURI + ":" + clientId+":"+contextId; + if (!connections.containsKey(clientHandle)) { + MqttConnection client = new MqttConnection(this, serverURI, + clientId, persistence, clientHandle); + connections.put(clientHandle, client); + } + return clientHandle; + } + + /** + * Connect to the MQTT server specified by a particular client + * + * @param clientHandle + * identifies the MqttConnection to use + * @param connectOptions + * the MQTT connection options to be used + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + * @throws MqttSecurityException thrown if there is a security exception + * @throws MqttException thrown for all other MqttExceptions + */ + public void connect(String clientHandle, MqttConnectOptions connectOptions, + String invocationContext, String activityToken) + throws MqttSecurityException, MqttException { + MqttConnection client = getConnection(clientHandle); + client.connect(connectOptions, null, activityToken); + + } + + /** + * Request all clients to reconnect if appropriate + */ + void reconnect() { + traceDebug(TAG, "Reconnect to server, client size=" + connections.size()); + for (MqttConnection client : connections.values()) { + traceDebug("Reconnect Client:", + client.getClientId() + '/' + client.getServerURI()); + if(this.isOnline()){ + client.reconnect(); + } + } + } + + /** + * Close connection from a particular client + * + * @param clientHandle + * identifies the MqttConnection to use + */ + public void close(String clientHandle) { + MqttConnection client = getConnection(clientHandle); + client.close(); + } + + /** + * Disconnect from the server + * + * @param clientHandle + * identifies the MqttConnection to use + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + public void disconnect(String clientHandle, String invocationContext, + String activityToken) { + MqttConnection client = getConnection(clientHandle); + client.disconnect(invocationContext, activityToken); + connections.remove(clientHandle); + + + // the activity has finished using us, so we can stop the service + // the activities are bound with BIND_AUTO_CREATE, so the service will + // remain around until the last activity disconnects + stopSelf(); + } + + /** + * Disconnect from the server + * + * @param clientHandle + * identifies the MqttConnection to use + * @param quiesceTimeout + * in milliseconds + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + public void disconnect(String clientHandle, long quiesceTimeout, + String invocationContext, String activityToken) { + MqttConnection client = getConnection(clientHandle); + client.disconnect(quiesceTimeout, invocationContext, activityToken); + connections.remove(clientHandle); + + // the activity has finished using us, so we can stop the service + // the activities are bound with BIND_AUTO_CREATE, so the service will + // remain around until the last activity disconnects + stopSelf(); + } + + /** + * Get the status of a specific client + * + * @param clientHandle + * identifies the MqttConnection to use + * @return true if the specified client is connected to an MQTT server + */ + public boolean isConnected(String clientHandle) { + MqttConnection client = getConnection(clientHandle); + return client.isConnected(); + } + + /** + * Publish a message to a topic + * + * @param clientHandle + * identifies the MqttConnection to use + * @param topic + * the topic to which to publish + * @param payload + * the content of the message to publish + * @param qos + * the quality of service requested + * @param retained + * whether the MQTT server should retain this message + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + * @throws MqttPersistenceException when a problem occurs storing the message + * @throws MqttException if there was an error publishing the message + * @return token for tracking the operation + */ + public IMqttDeliveryToken publish(String clientHandle, String topic, + byte[] payload, int qos, boolean retained, + String invocationContext, String activityToken) + throws MqttPersistenceException, MqttException { + MqttConnection client = getConnection(clientHandle); + return client.publish(topic, payload, qos, retained, invocationContext, + activityToken); + } + + /** + * Publish a message to a topic + * + * @param clientHandle + * identifies the MqttConnection to use + * @param topic + * the topic to which to publish + * @param message + * the message to publish + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + * @throws MqttPersistenceException when a problem occurs storing the message + * @throws MqttException if there was an error publishing the message + * @return token for tracking the operation + */ + public IMqttDeliveryToken publish(String clientHandle, String topic, + MqttMessage message, String invocationContext, String activityToken) + throws MqttPersistenceException, MqttException { + MqttConnection client = getConnection(clientHandle); + return client.publish(topic, message, invocationContext, activityToken); + } + + /** + * Subscribe to a topic + * + * @param clientHandle + * identifies the MqttConnection to use + * @param topic + * a possibly wildcarded topic name + * @param qos + * requested quality of service for the topic + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + public void subscribe(String clientHandle, String topic, int qos, + String invocationContext, String activityToken) { + MqttConnection client = getConnection(clientHandle); + client.subscribe(topic, qos, invocationContext, activityToken); + } + + /** + * Subscribe to one or more topics + * + * @param clientHandle + * identifies the MqttConnection to use + * @param topic + * a list of possibly wildcarded topic names + * @param qos + * requested quality of service for each topic + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + public void subscribe(String clientHandle, String[] topic, int[] qos, + String invocationContext, String activityToken) { + MqttConnection client = getConnection(clientHandle); + client.subscribe(topic, qos, invocationContext, activityToken); + } + + /** + * Subscribe using topic filters + * + * @param clientHandle + * identifies the MqttConnection to use + * @param topicFilters + * a list of possibly wildcarded topicfilters + * @param qos + * requested quality of service for each topic + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + * @param messageListeners a callback to handle incoming messages + */ + public void subscribe(String clientHandle, String[] topicFilters, int[] qos, String invocationContext, String activityToken, IMqttMessageListener[] messageListeners){ + MqttConnection client = getConnection(clientHandle); + client.subscribe(topicFilters, qos, invocationContext, activityToken, messageListeners); + } + + /** + * Unsubscribe from a topic + * + * @param clientHandle + * identifies the MqttConnection + * @param topic + * a possibly wildcarded topic name + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + public void unsubscribe(String clientHandle, final String topic, + String invocationContext, String activityToken) { + MqttConnection client = getConnection(clientHandle); + client.unsubscribe(topic, invocationContext, activityToken); + } + + /** + * Unsubscribe from one or more topics + * + * @param clientHandle + * identifies the MqttConnection + * @param topic + * a list of possibly wildcarded topic names + * @param invocationContext + * arbitrary data to be passed back to the application + * @param activityToken + * arbitrary identifier to be passed back to the Activity + */ + public void unsubscribe(String clientHandle, final String[] topic, + String invocationContext, String activityToken) { + MqttConnection client = getConnection(clientHandle); + client.unsubscribe(topic, invocationContext, activityToken); + } + + /** + * Get tokens for all outstanding deliveries for a client + * + * @param clientHandle + * identifies the MqttConnection + * @return an array (possibly empty) of tokens + */ + public IMqttDeliveryToken[] getPendingDeliveryTokens(String clientHandle) { + MqttConnection client = getConnection(clientHandle); + return client.getPendingDeliveryTokens(); + } + + /** + * Get the MqttConnection identified by this client handle + * + * @param clientHandle identifies the MqttConnection + * @return the MqttConnection identified by this handle + */ + private MqttConnection getConnection(String clientHandle) { + MqttConnection client = connections.get(clientHandle); + if (client == null) { + throw new IllegalArgumentException("Invalid ClientHandle"); + } + return client; + } + + /** + * Called by the Activity when a message has been passed back to the + * application + * + * @param clientHandle identifier for the client which received the message + * @param id identifier for the MQTT message + * @return {@link Status} + */ + public Status acknowledgeMessageArrival(String clientHandle, String id) { + if (messageStore.discardArrived(clientHandle, id)) { + return Status.OK; + } + else { + return Status.ERROR; + } + } + + // Extend Service + + /** + * @see android.app.Service#onCreate() + */ + @Override + public void onCreate() { + super.onCreate(); + + // create a binder that will let the Activity UI send + // commands to the Service + mqttServiceBinder = new MqttServiceBinder(this); + + // create somewhere to buffer received messages until + // we know that they have been passed to the application + messageStore = new DatabaseMessageStore(this, this); + } + + + + /** + * @see android.app.Service#onDestroy() + */ + @Override + public void onDestroy() { + // disconnect immediately + for (MqttConnection client : connections.values()) { + client.disconnect(null, null); + } + + // clear down + if (mqttServiceBinder != null) { + mqttServiceBinder = null; + } + + unregisterBroadcastReceivers(); + + if (this.messageStore !=null ) + this.messageStore.close(); + + super.onDestroy(); + } + + /** + * @see android.app.Service#onBind(Intent) + */ + @Override + public IBinder onBind(Intent intent) { + // What we pass back to the Activity on binding - + // a reference to ourself, and the activityToken + // we were given when started + String activityToken = intent + .getStringExtra(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN); + mqttServiceBinder.setActivityToken(activityToken); + return mqttServiceBinder; + } + + /** + * @see android.app.Service#onStartCommand(Intent,int,int) + */ + @Override + public int onStartCommand(final Intent intent, int flags, final int startId) { + // run till explicitly stopped, restart when + // process restarted + registerBroadcastReceivers(); + + return START_STICKY; + } + + /** + * Identify the callbackId to be passed when making tracing calls back into + * the Activity + * + * @param traceCallbackId identifier to the callback into the Activity + */ + public void setTraceCallbackId(String traceCallbackId) { + this.traceCallbackId = traceCallbackId; + } + + /** + * Turn tracing on and off + * + * @param traceEnabled set true to turn on tracing, false to turn off tracing + */ + public void setTraceEnabled(boolean traceEnabled) { + this.traceEnabled = traceEnabled; + } + + /** + * Check whether trace is on or off. + * + * @return the state of trace + */ + public boolean isTraceEnabled(){ + return this.traceEnabled; + } + + /** + * Trace debugging information + * + * @param tag + * identifier for the source of the trace + * @param message + * the text to be traced + */ + @Override + public void traceDebug(String tag, String message) { + traceCallback(MqttServiceConstants.TRACE_DEBUG, tag, message); + } + + /** + * Trace error information + * + * @param tag + * identifier for the source of the trace + * @param message + * the text to be traced + */ + @Override + public void traceError(String tag, String message) { + traceCallback(MqttServiceConstants.TRACE_ERROR, tag, message); + } + + private void traceCallback(String severity, String tag, String message) { + if ((traceCallbackId != null) && (traceEnabled)) { + Bundle dataBundle = new Bundle(); + dataBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.TRACE_ACTION); + dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY, severity); + dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_TAG, tag); + //dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_ID, traceCallbackId); + dataBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, message); + callbackToActivity(traceCallbackId, Status.ERROR, dataBundle); + } + } + + /** + * trace exceptions + * + * @param tag + * identifier for the source of the trace + * @param message + * the text to be traced + * @param e + * the exception + */ + @Override + public void traceException(String tag, String message, Exception e) { + if (traceCallbackId != null) { + Bundle dataBundle = new Bundle(); + dataBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.TRACE_ACTION); + dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY, MqttServiceConstants.TRACE_EXCEPTION); + dataBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, message); + dataBundle.putSerializable(MqttServiceConstants.CALLBACK_EXCEPTION, e); //TODO: Check + dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_TAG, tag); + //dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_ID, traceCallbackId); + callbackToActivity(traceCallbackId, Status.ERROR, dataBundle); + } + } + + @SuppressWarnings("deprecation") + private void registerBroadcastReceivers() { + if (networkConnectionMonitor == null) { + networkConnectionMonitor = new NetworkConnectionIntentReceiver(); + registerReceiver(networkConnectionMonitor, new IntentFilter( + ConnectivityManager.CONNECTIVITY_ACTION)); + } + + if (Build.VERSION.SDK_INT < 14 /**Build.VERSION_CODES.ICE_CREAM_SANDWICH**/) { + // Support the old system for background data preferences + ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); + backgroundDataEnabled = cm.getBackgroundDataSetting(); + if (backgroundDataPreferenceMonitor == null) { + backgroundDataPreferenceMonitor = new BackgroundDataPreferenceReceiver(); + registerReceiver( + backgroundDataPreferenceMonitor, + new IntentFilter( + ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED)); + } + } + } + + private void unregisterBroadcastReceivers(){ + if(networkConnectionMonitor != null){ + unregisterReceiver(networkConnectionMonitor); + networkConnectionMonitor = null; + } + + if (Build.VERSION.SDK_INT < 14 /**Build.VERSION_CODES.ICE_CREAM_SANDWICH**/) { + if(backgroundDataPreferenceMonitor != null){ + unregisterReceiver(backgroundDataPreferenceMonitor); + } + } + } + + /* + * Called in response to a change in network connection - after losing a + * connection to the server, this allows us to wait until we have a usable + * data connection again + */ + private class NetworkConnectionIntentReceiver extends BroadcastReceiver { + + @Override + @SuppressLint("Wakelock") + public void onReceive(Context context, Intent intent) { + traceDebug(TAG, "Internal network status receive."); + // we protect against the phone switching off + // by requesting a wake lock - we request the minimum possible wake + // lock - just enough to keep the CPU running until we've finished + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + WakeLock wl = pm + .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MQTT"); + wl.acquire(); + traceDebug(TAG,"Reconnect for Network recovery."); + if (isOnline()) { + traceDebug(TAG,"Online,reconnect."); + // we have an internet connection - have another try at + // connecting + reconnect(); + } else { + notifyClientsOffline(); + } + + wl.release(); + } + } + + /** + * @return whether the android service can be regarded as online + */ + public boolean isOnline() { + ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + //noinspection RedundantIfStatement + if (networkInfo != null + && networkInfo.isAvailable() + && networkInfo.isConnected() + && backgroundDataEnabled) { + return true; + } + + return false; + } + + /** + * Notify clients we're offline + */ + private void notifyClientsOffline() { + for (MqttConnection connection : connections.values()) { + connection.offline(); + } + } + + /** + * Detect changes of the Allow Background Data setting - only used below + * ICE_CREAM_SANDWICH + */ + private class BackgroundDataPreferenceReceiver extends BroadcastReceiver { + + @SuppressWarnings("deprecation") + @Override + public void onReceive(Context context, Intent intent) { + ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); + traceDebug(TAG,"Reconnect since BroadcastReceiver."); + if (cm.getBackgroundDataSetting()) { + if (!backgroundDataEnabled) { + backgroundDataEnabled = true; + // we have the Internet connection - have another try at + // connecting + reconnect(); + } + } else { + backgroundDataEnabled = false; + notifyClientsOffline(); + } + } + } + + /** + * Sets the DisconnectedBufferOptions for this client + * @param clientHandle identifier for the client + * @param bufferOpts the DisconnectedBufferOptions for this client + */ + public void setBufferOpts(String clientHandle, DisconnectedBufferOptions bufferOpts) { + MqttConnection client = getConnection(clientHandle); + client.setBufferOpts(bufferOpts); + } + + public int getBufferedMessageCount(String clientHandle){ + MqttConnection client = getConnection(clientHandle); + return client.getBufferedMessageCount(); + } + + public MqttMessage getBufferedMessage(String clientHandle, int bufferIndex){ + MqttConnection client = getConnection(clientHandle); + return client.getBufferedMessage(bufferIndex); + } + + public void deleteBufferedMessage(String clientHandle, int bufferIndex){ + MqttConnection client = getConnection(clientHandle); + client.deleteBufferedMessage(bufferIndex); + } + +} diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttServiceBinder.java b/app/src/main/java/org/eclipse/paho/android/service/MqttServiceBinder.java new file mode 100644 index 0000000..6dbf3fd --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/MqttServiceBinder.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 1999, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.paho.android.service; + +import android.os.Binder; + +/** + * What the Service passes to the Activity on binding:- + *
    + *
  • a reference to the Service + *
  • the activityToken provided when the Service was started + *
+ * + */ +class MqttServiceBinder extends Binder { + + private MqttService mqttService; + private String activityToken; + + MqttServiceBinder(MqttService mqttService) { + this.mqttService = mqttService; + } + + /** + * @return a reference to the Service + */ + public MqttService getService() { + return mqttService; + } + + void setActivityToken(String activityToken) { + this.activityToken = activityToken; + } + + /** + * @return the activityToken provided when the Service was started + */ + public String getActivityToken() { + return activityToken; + } + +} diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttServiceConstants.java b/app/src/main/java/org/eclipse/paho/android/service/MqttServiceConstants.java new file mode 100644 index 0000000..b05feb9 --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/MqttServiceConstants.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 1999, 2016 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.paho.android.service; + +/** + * Various strings used to identify operations or data in the Android MQTT + * service, mainly used in Intents passed between Activities and the Service. + */ +interface MqttServiceConstants { + + /* + * Version information + */ + + String VERSION = "v0"; + + /* + * Attributes of messages

Used for the column names in the database + */ + String DUPLICATE = "duplicate"; + String RETAINED = "retained"; + String QOS = "qos"; + String PAYLOAD = "payload"; + String DESTINATION_NAME = "destinationName"; + String CLIENT_HANDLE = "clientHandle"; + String MESSAGE_ID = "messageId"; + + /* Tags for actions passed between the Activity and the Service */ + String SEND_ACTION = "send"; + String UNSUBSCRIBE_ACTION = "unsubscribe"; + String SUBSCRIBE_ACTION = "subscribe"; + String DISCONNECT_ACTION = "disconnect"; + String CONNECT_ACTION = "connect"; + String CONNECT_EXTENDED_ACTION = "connectExtended"; + String MESSAGE_ARRIVED_ACTION = "messageArrived"; + String MESSAGE_DELIVERED_ACTION = "messageDelivered"; + String ON_CONNECTION_LOST_ACTION = "onConnectionLost"; + String TRACE_ACTION = "trace"; + + /* Identifies an Intent which calls back to the Activity */ + String CALLBACK_TO_ACTIVITY = MqttService.TAG + + ".callbackToActivity"+"."+VERSION; + + /* Identifiers for extra data on Intents broadcast to the Activity */ + String CALLBACK_ACTION = MqttService.TAG + ".callbackAction"; + String CALLBACK_STATUS = MqttService.TAG + ".callbackStatus"; + String CALLBACK_CLIENT_HANDLE = MqttService.TAG + "." + + CLIENT_HANDLE; + String CALLBACK_ERROR_MESSAGE = MqttService.TAG + + ".errorMessage"; + String CALLBACK_EXCEPTION_STACK = MqttService.TAG + + ".exceptionStack"; + String CALLBACK_INVOCATION_CONTEXT = MqttService.TAG + "." + + "invocationContext"; + String CALLBACK_ACTIVITY_TOKEN = MqttService.TAG + "." + + "activityToken"; + String CALLBACK_DESTINATION_NAME = MqttService.TAG + '.' + + DESTINATION_NAME; + String CALLBACK_MESSAGE_ID = MqttService.TAG + '.' + + MESSAGE_ID; + String CALLBACK_RECONNECT = MqttService.TAG + ".reconnect"; + String CALLBACK_SERVER_URI = MqttService.TAG + ".serverURI"; + String CALLBACK_MESSAGE_PARCEL = MqttService.TAG + ".PARCEL"; + String CALLBACK_TRACE_SEVERITY = MqttService.TAG + + ".traceSeverity"; + String CALLBACK_TRACE_TAG = MqttService.TAG + ".traceTag"; + String CALLBACK_TRACE_ID = MqttService.TAG + ".traceId"; + String CALLBACK_ERROR_NUMBER = MqttService.TAG + + ".ERROR_NUMBER"; + + String CALLBACK_EXCEPTION = MqttService.TAG + ".exception"; + + //Intent prefix for Ping sender. + String PING_SENDER = MqttService.TAG + ".pingSender."; + + //Constant for wakelock + String PING_WAKELOCK = MqttService.TAG + ".client."; + String WAKELOCK_NETWORK_INTENT = MqttService.TAG + ""; + + //Trace severity levels + String TRACE_ERROR = "error"; + String TRACE_DEBUG = "debug"; + String TRACE_EXCEPTION = "exception"; + + + //exception code for non MqttExceptions + int NON_MQTT_EXCEPTION = -1; + +} \ No newline at end of file diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttTokenAndroid.java b/app/src/main/java/org/eclipse/paho/android/service/MqttTokenAndroid.java new file mode 100644 index 0000000..01a357a --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/MqttTokenAndroid.java @@ -0,0 +1,252 @@ +/******************************************************************************* + * Copyright (c) 1999, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.paho.android.service; + +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttAsyncClient; +import org.eclipse.paho.client.mqttv3.IMqttToken; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttSecurityException; +import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage; + +/** + *

+ * Implementation of the IMqttToken interface for use from within the + * MqttAndroidClient implementation + */ + +class MqttTokenAndroid implements IMqttToken { + + private IMqttActionListener listener; + + private volatile boolean isComplete; + + private volatile MqttException lastException; + + private Object waitObject = new Object(); + + private MqttAndroidClient client; + + private Object userContext; + + private String[] topics; + + private IMqttToken delegate; // specifically for getMessageId + + private MqttException pendingException; + + /** + * Standard constructor + * + * @param client used to pass MqttAndroidClient object + * @param userContext used to pass context + * @param listener optional listener that will be notified when the action completes. Use null if not required. + */ + MqttTokenAndroid(MqttAndroidClient client, + Object userContext, IMqttActionListener listener) { + this(client, userContext, listener, null); + } + + /** + * Constructor for use with subscribe operations + * + * @param client used to pass MqttAndroidClient object + * @param userContext used to pass context + * @param listener optional listener that will be notified when the action completes. Use null if not required. + * @param topics topics to subscribe to, which can include wildcards. + */ + MqttTokenAndroid(MqttAndroidClient client, + Object userContext, IMqttActionListener listener, String[] topics) { + this.client = client; + this.userContext = userContext; + this.listener = listener; + this.topics = topics; + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#waitForCompletion() + */ + @Override + public void waitForCompletion() throws MqttException, MqttSecurityException { + synchronized (waitObject) { + try { + waitObject.wait(); + } + catch (InterruptedException e) { + // do nothing + } + } + if (pendingException != null) { + throw pendingException; + } + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#waitForCompletion(long) + */ + @Override + public void waitForCompletion(long timeout) throws MqttException, + MqttSecurityException { + synchronized (waitObject) { + try { + waitObject.wait(timeout); + } + catch (InterruptedException e) { + // do nothing + } + if (!isComplete) { + throw new MqttException(MqttException.REASON_CODE_CLIENT_TIMEOUT); + } + if (pendingException != null) { + throw pendingException; + } + } + } + + /** + * notify successful completion of the operation + */ + void notifyComplete() { + synchronized (waitObject) { + isComplete = true; + waitObject.notifyAll(); + if (listener != null) { + listener.onSuccess(this); + } + } + } + + /** + * notify unsuccessful completion of the operation + */ + void notifyFailure(Throwable exception) { + synchronized (waitObject) { + isComplete = true; + if (exception instanceof MqttException) { + pendingException = (MqttException) exception; + } + else { + pendingException = new MqttException(exception); + } + waitObject.notifyAll(); + if (exception instanceof MqttException) { + lastException = (MqttException) exception; + } + if (listener != null) { + listener.onFailure(this, exception); + } + } + + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#isComplete() + */ + @Override + public boolean isComplete() { + return isComplete; + } + + void setComplete(boolean complete) { + isComplete = complete; + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#getException() + */ + @Override + public MqttException getException() { + return lastException; + } + + void setException(MqttException exception) { + lastException = exception; + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#getClient() + */ + @Override + public IMqttAsyncClient getClient() { + return client; + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#setActionCallback(IMqttActionListener) + */ + @Override + public void setActionCallback(IMqttActionListener listener) { + this.listener = listener; + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#getActionCallback() + */ + @Override + public IMqttActionListener getActionCallback() { + return listener; + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#getTopics() + */ + @Override + public String[] getTopics() { + return topics; + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#setUserContext(Object) + */ + @Override + public void setUserContext(Object userContext) { + this.userContext = userContext; + + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#getUserContext() + */ + @Override + public Object getUserContext() { + return userContext; + } + + void setDelegate(IMqttToken delegate) { + this.delegate = delegate; + } + + /** + * @see org.eclipse.paho.client.mqttv3.IMqttToken#getMessageId() + */ + @Override + public int getMessageId() { + return (delegate != null) ? delegate.getMessageId() : 0; + } + + @Override + public MqttWireMessage getResponse() { + return delegate.getResponse(); + } + + @Override + public boolean getSessionPresent() { + return delegate.getSessionPresent(); + } + + @Override + public int[] getGrantedQos() { + return delegate.getGrantedQos(); + } + +} diff --git a/app/src/main/java/org/eclipse/paho/android/service/MqttTraceHandler.java b/app/src/main/java/org/eclipse/paho/android/service/MqttTraceHandler.java new file mode 100644 index 0000000..8118c43 --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/MqttTraceHandler.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 1999, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.paho.android.service; + +/** + * Interface for simple trace handling, pass the trace message to trace + * callback. + * + */ + +public interface MqttTraceHandler { + + /** + * Trace debugging information + * + * @param tag + * identifier for the source of the trace + * @param message + * the text to be traced + */ + void traceDebug(String tag, String message); + + /** + * Trace error information + * + * @param tag + * identifier for the source of the trace + * @param message + * the text to be traced + */ + void traceError(String tag, String message); + + /** + * trace exceptions + * + * @param tag + * identifier for the source of the trace + * @param message + * the text to be traced + * @param e + * the exception + */ + void traceException(String tag, String message, + Exception e); + +} \ No newline at end of file diff --git a/app/src/main/java/org/eclipse/paho/android/service/ParcelableMqttMessage.java b/app/src/main/java/org/eclipse/paho/android/service/ParcelableMqttMessage.java new file mode 100644 index 0000000..9164cee --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/ParcelableMqttMessage.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 1999, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.paho.android.service; + +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + *

+ * A way to flow MqttMessages via Bundles/Intents + *

+ * + *

+ * An application will probably use this only when receiving a message from a + * Service in a Bundle - the necessary code will be something like this :- + *

+ *
+ * 
+ * 	private void messageArrivedAction(Bundle data) {
+ * 		ParcelableMqttMessage message = (ParcelableMqttMessage) data
+ * 			.getParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL);
+ *		Use the normal {@link MqttMessage} methods on the the message object.
+ * 	}
+ * 
+ * 
+ * 
+ * + *

+ * It is unlikely that an application will directly use the methods which are + * specific to this class. + *

+ */ + +public class ParcelableMqttMessage extends MqttMessage implements Parcelable { + + String messageId = null; + + ParcelableMqttMessage(MqttMessage original) { + super(original.getPayload()); + setQos(original.getQos()); + setRetained(original.isRetained()); + setDuplicate(original.isDuplicate()); + } + + ParcelableMqttMessage(Parcel parcel) { + super(parcel.createByteArray()); + setQos(parcel.readInt()); + boolean[] flags = parcel.createBooleanArray(); + setRetained(flags[0]); + setDuplicate(flags[1]); + messageId = parcel.readString(); + } + + /** + * @return the messageId + */ + public String getMessageId() { + return messageId; + } + + /** + * Describes the contents of this object + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Writes the contents of this object to a parcel + * + * @param parcel + * The parcel to write the data to. + * @param flags + * this parameter is ignored + */ + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeByteArray(getPayload()); + parcel.writeInt(getQos()); + parcel.writeBooleanArray(new boolean[]{isRetained(), isDuplicate()}); + parcel.writeString(messageId); + } + + /** + * A creator which creates the message object from a parcel + */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + /** + * Creates a message from the parcel object + */ + @Override + public ParcelableMqttMessage createFromParcel(Parcel parcel) { + return new ParcelableMqttMessage(parcel); + } + + /** + * creates an array of type {@link ParcelableMqttMessage}[] + * + */ + @Override + public ParcelableMqttMessage[] newArray(int size) { + return new ParcelableMqttMessage[size]; + } + }; +} diff --git a/app/src/main/java/org/eclipse/paho/android/service/RitoPingSender.java b/app/src/main/java/org/eclipse/paho/android/service/RitoPingSender.java new file mode 100644 index 0000000..7c22210 --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/RitoPingSender.java @@ -0,0 +1,55 @@ +package org.eclipse.paho.android.service; + +import org.eclipse.paho.client.mqttv3.MqttPingSender; +import org.eclipse.paho.client.mqttv3.internal.ClientComms; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class RitoPingSender implements MqttPingSender { + + private ClientComms comms; + private ScheduledExecutorService scheduler; + private ScheduledFuture pingFuture; + + @Override + public void init(ClientComms comms) { + this.comms = comms; + this.scheduler = Executors.newSingleThreadScheduledExecutor(); + } + + @Override + public void start() { + schedule(comms.getKeepAlive()); + } + + private void schedule(int delayInSeconds) { + pingFuture = scheduler.scheduleAtFixedRate(() -> { + try { + comms.checkForActivity(); + } catch (Exception e) { + e.printStackTrace(); + } + }, delayInSeconds, delayInSeconds, TimeUnit.SECONDS); + } + + @Override + public void stop() { + if (pingFuture != null) pingFuture.cancel(true); + scheduler.shutdownNow(); + } + + @Override + public void schedule(long delayInMilliseconds) { + if (pingFuture != null) pingFuture.cancel(true); + pingFuture = scheduler.schedule(() -> { + try { + comms.checkForActivity(); + } catch (Exception e) { + e.printStackTrace(); + } + }, delayInMilliseconds, TimeUnit.MILLISECONDS); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/eclipse/paho/android/service/Status.java b/app/src/main/java/org/eclipse/paho/android/service/Status.java new file mode 100644 index 0000000..dadde36 --- /dev/null +++ b/app/src/main/java/org/eclipse/paho/android/service/Status.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 1999, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.paho.android.service; + +/** + * Enumeration representing the success or failure of an operation + */ +enum Status { + /** + * Indicates that the operation succeeded + */ + OK, + + /** + * Indicates that the operation failed + */ + ERROR, + + /** + * Indicates that the operation's result may be returned asynchronously + */ + NO_RESULT +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/blackground.png b/app/src/main/res/drawable/blackground.png new file mode 100644 index 0000000..a869da0 Binary files /dev/null and b/app/src/main/res/drawable/blackground.png differ diff --git a/app/src/main/res/drawable/channel_osd.png b/app/src/main/res/drawable/channel_osd.png new file mode 100644 index 0000000..d8eca67 Binary files /dev/null and b/app/src/main/res/drawable/channel_osd.png differ diff --git a/app/src/main/res/drawable/demo_119.png b/app/src/main/res/drawable/demo_119.png new file mode 100644 index 0000000..fbf1f05 Binary files /dev/null and b/app/src/main/res/drawable/demo_119.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/no_broadcast.png b/app/src/main/res/drawable/no_broadcast.png new file mode 100644 index 0000000..d390745 Binary files /dev/null and b/app/src/main/res/drawable/no_broadcast.png differ diff --git a/app/src/main/res/drawable/on_tts.png b/app/src/main/res/drawable/on_tts.png new file mode 100644 index 0000000..391af2a Binary files /dev/null and b/app/src/main/res/drawable/on_tts.png differ diff --git a/app/src/main/res/drawable/selector_button.xml b/app/src/main/res/drawable/selector_button.xml new file mode 100644 index 0000000..0d6e48d --- /dev/null +++ b/app/src/main/res/drawable/selector_button.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/splash.png b/app/src/main/res/drawable/splash.png new file mode 100644 index 0000000..39d030e Binary files /dev/null and b/app/src/main/res/drawable/splash.png differ diff --git a/app/src/main/res/drawable/wait_broadcast.png b/app/src/main/res/drawable/wait_broadcast.png new file mode 100644 index 0000000..1bab36c Binary files /dev/null and b/app/src/main/res/drawable/wait_broadcast.png differ diff --git a/app/src/main/res/drawable/webview_loading.png b/app/src/main/res/drawable/webview_loading.png new file mode 100644 index 0000000..56a708a Binary files /dev/null and b/app/src/main/res/drawable/webview_loading.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..390c91f --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml new file mode 100644 index 0000000..9418564 --- /dev/null +++ b/app/src/main/res/layout/activity_setting.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_setting.xml b/app/src/main/res/layout/fragment_setting.xml new file mode 100644 index 0000000..bef5834 --- /dev/null +++ b/app/src/main/res/layout/fragment_setting.xml @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + +