OSDN Git Service

とりあえずコンパイルできるように。
authorMRSa <mrsa@myad.jp>
Wed, 10 Feb 2021 14:26:26 +0000 (23:26 +0900)
committerMRSa <mrsa@myad.jp>
Wed, 10 Feb 2021 14:26:26 +0000 (23:26 +0900)
20 files changed:
.idea/gradle.xml
README.txt
app/build.gradle
app/src/main/AndroidManifest.xml
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/MainActivity.kt
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/ITextDataUpdater.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/MyBluetoothAdapter.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/connection/BluetoothDeviceFinder.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/connection/IBluetoothScanResult.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/connection/eeg/MindWaveConnection.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/BrainwaveDataHolder.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/BrainwaveFileLogger.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/BrainwaveSummaryData.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/IBrainwaveDataReceiver.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SimpleHttpClient.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SimpleLiveViewSlicer.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SimpleLogDumper.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SnackBarMessage.kt [new file with mode: 0644]
app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/XmlElement.kt [new file with mode: 0644]
app/src/main/res/values/strings.xml

index fa31fe2..339fe25 100644 (file)
@@ -1,9 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
         <option name="testRunner" value="PLATFORM" />
+        <option name="disableWrapperSourceDistributionNotification" value="true" />
         <option name="distributionType" value="DEFAULT_WRAPPED" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="gradleJvm" value="1.8 (2)" />
index e69de29..a73d748 100644 (file)
@@ -0,0 +1,2 @@
+Theta 'Thought' Shutter : An EEG shutter PlugIn for the THETA.
+
index 8ad046e..a3f528a 100644 (file)
@@ -9,10 +9,10 @@ android {
 
     defaultConfig {
         applicationId "jp.osdn.gokigen.thetathoughtshutter"
-        minSdkVersion 24
+        minSdkVersion 25
         targetSdkVersion 30
-        versionCode 10000
-        versionName "1.0.00"
+        versionCode 10000000
+        versionName "10.00.0001"
     }
 
     buildTypes {
index eaca4f4..9664e77 100644 (file)
@@ -2,8 +2,16 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="jp.osdn.gokigen.thetathoughtshutter">
 
+    <uses-feature android:name="com.theta360.receptor.v" android:required="true"/>
+    <uses-feature android:name="com.theta360.receptor.z1" android:required="true"/>
+
+    <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
     <application
         android:allowBackup="false"
index 784d98e..943b028 100644 (file)
@@ -30,11 +30,24 @@ class MainActivity : PluginActivity()
             override fun onKeyUp(keyCode: Int, event: KeyEvent?)
             {
                 notificationLedBlink(LedTarget.LED3, LedColor.BLUE, 1000)
+                //notificationLedBlink(LedTarget.LED4, LedColor.CYAN, 1000)
             }
 
             override fun onKeyLongPress(keyCode: Int, event: KeyEvent?)
             {
-                if (keyCode == KeyReceiver.KEYCODE_MEDIA_RECORD)
+                if (keyCode == KeyReceiver.KEYCODE_MEDIA_RECORD) // Modeボタン
+                {
+
+                }
+                if (keyCode == KeyReceiver.KEYCODE_CAMERA)   // Shutterボタン
+                {
+
+                }
+                if (keyCode == KeyReceiver.KEYCODE_FUNCTION)   // Fnボタン (Z1のみ)
+                {
+
+                }
+                if (keyCode == KeyReceiver.KEYCODE_WLAN_ON_OFF) // Wirelessボタン
                 {
 
                 }
@@ -56,4 +69,34 @@ class MainActivity : PluginActivity()
         super.onPause()
     }
 
-}
\ No newline at end of file
+}
+
+//
+// -----------------------------------------------------
+//  LED1 : 電源ランプ
+//  LED2 : カメラステータス ランプ(レンズとマイクの間)
+//  LED3 : ワイヤレスマーク ランプ
+//  LED4 : キャプチャーモード (カメラ)
+//  LED5 : キャプチャーモード (ムービー)
+//  LED6 : キャプチャーモード (LIVEストリーミング)
+//  LED7 : ビデオ録画 ランプ
+//  LED8 : メモリ警告ランプ
+//
+//  BTN1 : 電源ボタン
+//  BTN2 : ワイヤレスボタン
+//  BTN3 : モードボタン
+//  SHUT : シャッターボタン
+// -----------------------------------------------------
+//
+//  [制御可能なLED]
+//    - LED3~LED8 : カラー : "blue", "green", "red", "cyan", "magenta", "yellow", "white"
+//    - ブリンク間隔 : 1~2000 msec
+//
+//  [KeyCode]
+//    - 27  : Shutter Button
+//    - 130 : Mode Button
+//    - 284 : Wireless Button
+//    - 119 : Fn Button (Z1 Only)
+//
+//
+//
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/ITextDataUpdater.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/ITextDataUpdater.kt
new file mode 100644 (file)
index 0000000..9ba9e3e
--- /dev/null
@@ -0,0 +1,10 @@
+package jp.osdn.gokigen.thetathoughtshutter.bluetooth
+
+interface ITextDataUpdater
+{
+    fun setText(data: String?)
+    fun addText(data: String?)
+    fun showSnackBar(message: String?)
+    fun showSnackBar(rscId: Int)
+    fun enableOperation(isEnable: Boolean)
+}
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/MyBluetoothAdapter.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/MyBluetoothAdapter.kt
new file mode 100644 (file)
index 0000000..80d4e34
--- /dev/null
@@ -0,0 +1,26 @@
+package jp.osdn.gokigen.thetathoughtshutter.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+
+
+class MyBluetoothAdapter
+{
+    fun getBondedDevices(): List<String>
+    {
+        val s: MutableList<String> = ArrayList()
+        try
+        {
+            val btAdapter = BluetoothAdapter.getDefaultAdapter()
+            val bondedDevices = btAdapter.bondedDevices
+            for (bt in bondedDevices)
+            {
+                s.add(bt.name)
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        return (s)
+    }
+}
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/connection/BluetoothDeviceFinder.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/connection/BluetoothDeviceFinder.kt
new file mode 100644 (file)
index 0000000..d893c3b
--- /dev/null
@@ -0,0 +1,111 @@
+package jp.osdn.gokigen.thetathoughtshutter.bluetooth.connection
+
+import android.app.Activity
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothManager
+import android.content.Context
+import android.util.Log
+import jp.osdn.gokigen.thetathoughtshutter.R
+import jp.osdn.gokigen.thetathoughtshutter.utils.SnackBarMessage
+
+class BluetoothDeviceFinder(private val context: Activity, private val scanResult: IBluetoothScanResult) : BluetoothAdapter.LeScanCallback
+{
+
+    companion object
+    {
+        private val TAG = toString()
+        private const val BLE_SCAN_TIMEOUT_MILLIS = 15 * 1000 // 15秒間
+        private const val BLE_WAIT_DURATION = 100 // 100ms間隔
+    }
+    private lateinit var targetDeviceName: String
+    private val messageToShow = SnackBarMessage(context, false)
+    private var foundBleDevice = false
+
+    fun reset()
+    {
+        foundBleDevice = false
+    }
+
+    fun startScan(targetDeviceName: String)
+    {
+        try
+        {
+            this.targetDeviceName = targetDeviceName
+            val btAdapter = BluetoothAdapter.getDefaultAdapter()
+            if (!btAdapter.isEnabled)
+            {
+                // Bluetoothの設定がOFFだった
+                messageToShow.showMessage(R.string.bluetooth_setting_is_off)
+            }
+            // Bluetooth のサービスを取得
+            val btMgr: BluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
+            scanBluetoothDevice(btMgr)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    private fun scanBluetoothDevice(btMgr: BluetoothManager)
+    {
+        try
+        {
+            // スキャン開始
+            foundBleDevice = false
+            val adapter = btMgr.adapter
+            if (!adapter.startLeScan(this))
+            {
+                // Bluetooth LEのスキャンが開始できなかった場合...
+                Log.v(TAG, "Bluetooth LE SCAN START fail...")
+                messageToShow.showMessage(R.string.bluetooth_scan_start_failure)
+                return
+            }
+            Log.v(TAG, " ----- BT SCAN STARTED ----- ")
+            var passed = 0
+            while (passed < BLE_SCAN_TIMEOUT_MILLIS)
+            {
+                if (foundBleDevice)
+                {
+                    // デバイス発見
+                    Log.v(TAG, "FOUND DEVICE")
+                    break
+                }
+
+                // BLEのスキャンが終わるまで待つ
+                Thread.sleep(BLE_WAIT_DURATION.toLong())
+                passed += BLE_WAIT_DURATION
+            }
+            // スキャンを止める(500ms後に)
+            Thread.sleep(500)
+            adapter.stopLeScan(this)
+            Log.v(TAG, " ----- BT SCAN STOPPED ----- ")
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+            Log.v(TAG, "Bluetooth LE SCAN EXCEPTION...")
+            messageToShow.showMessage(R.string.scan_fail_via_bluetooth)
+        }
+        Log.v(TAG, "Bluetooth SCAN STOPPED.")
+    }
+
+    override fun onLeScan(device: BluetoothDevice, rssi: Int, scanRecord: ByteArray?)
+    {
+        try
+        {
+            val btDeviceName = device.name
+            if (btDeviceName != null && btDeviceName.matches(Regex(targetDeviceName)))
+            {
+                // device発見!
+                foundBleDevice = true
+                scanResult.foundBluetoothDevice(device)
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+}
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/connection/IBluetoothScanResult.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/connection/IBluetoothScanResult.kt
new file mode 100644 (file)
index 0000000..7b4af2a
--- /dev/null
@@ -0,0 +1,8 @@
+package jp.osdn.gokigen.thetathoughtshutter.bluetooth.connection
+
+import android.bluetooth.BluetoothDevice
+
+interface IBluetoothScanResult
+{
+    fun foundBluetoothDevice(device: BluetoothDevice)
+}
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/connection/eeg/MindWaveConnection.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/bluetooth/connection/eeg/MindWaveConnection.kt
new file mode 100644 (file)
index 0000000..e07a173
--- /dev/null
@@ -0,0 +1,183 @@
+package jp.osdn.gokigen.thetathoughtshutter.bluetooth.connection.eeg
+
+import android.app.Activity
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothSocket
+import android.util.Log
+import jp.osdn.gokigen.thetathoughtshutter.bluetooth.connection.IBluetoothScanResult
+import jp.osdn.gokigen.thetathoughtshutter.bluetooth.connection.BluetoothDeviceFinder
+import jp.osdn.gokigen.thetathoughtshutter.brainwave.BrainwaveFileLogger
+import jp.osdn.gokigen.thetathoughtshutter.brainwave.IBrainwaveDataReceiver
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.util.*
+import kotlin.experimental.and
+
+
+class MindWaveConnection(private val context : Activity, private val dataReceiver: IBrainwaveDataReceiver) : IBluetoothScanResult
+{
+    companion object
+    {
+        private val TAG = toString()
+    }
+
+    private val deviceFinder: BluetoothDeviceFinder = BluetoothDeviceFinder(context, this)
+    private var fileLogger: BrainwaveFileLogger? = null
+    private var foundDevice = false
+    private var loggingFlag = false
+
+    fun connect(deviceName: String, loggingFlag: Boolean = false)
+    {
+        Log.v(TAG, " BrainWaveMobileCommunicator::connect() : $deviceName Logging : $loggingFlag")
+        try
+        {
+            this.loggingFlag = loggingFlag
+
+            // Bluetooth のサービスを取得、BLEデバイスをスキャンする
+            foundDevice = false
+            deviceFinder.reset()
+            deviceFinder.startScan(deviceName)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    private fun parseReceivedData(data: ByteArray)
+    {
+        // 受信データブロック1つ分
+        try
+        {
+            if (data.size <= 3)
+            {
+                // ヘッダ部しか入っていない...無視する
+                return
+            }
+            val length = data[2]
+            if (data.size < length + 2)
+            {
+                // データが最小サイズに満たない...無視する
+                return
+            }
+            if (data.size == 8 || data.size == 9)
+            {
+                var value: Int = (data[5] and 0xff.toByte()) * 256 + (data[6] and 0xff.toByte())
+                if (value > 32768)
+                {
+                    value -= 65536
+                }
+                dataReceiver.receivedRawData(value)
+                return
+            }
+            dataReceiver.receivedSummaryData(data)
+
+            // ファイルにサマリーデータを出力する
+            fileLogger?.outputSummaryData(data)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    private fun serialCommunicationMain(btSocket: BluetoothSocket)
+    {
+        var inputStream: InputStream? = null
+        try
+        {
+            btSocket.connect()
+            inputStream = btSocket.inputStream
+        }
+        catch (e: Exception)
+        {
+            Log.e(TAG, "Fail to accept.", e)
+        }
+        if (inputStream == null)
+        {
+            return
+        }
+        if (loggingFlag)
+        {
+            try
+            {
+                // ログ出力を指示されていた場合...ファイル出力クラスを作成しておく
+                fileLogger = BrainwaveFileLogger()
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+            }
+        }
+
+        // シリアルデータの受信メイン部分
+        var previousData = 0xff.toByte()
+        val outputStream = ByteArrayOutputStream()
+        while (foundDevice)
+        {
+            try
+            {
+                val data: Int = inputStream.read()
+                val byteData = (data and 0xff).toByte()
+                if (previousData == byteData && byteData == 0xaa.toByte())
+                {
+                    // 先頭データを見つけた。 (0xaa 0xaa がヘッダ)
+                    parseReceivedData(outputStream.toByteArray())
+                    outputStream.reset()
+                    outputStream.write(0xaa)
+                    outputStream.write(0xaa)
+                }
+                else
+                {
+                    outputStream.write(byteData.toInt())
+                }
+                previousData = byteData
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+            }
+        }
+        try
+        {
+            btSocket.close()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    override fun foundBluetoothDevice(device: BluetoothDevice)
+    {
+        try
+        {
+            if (foundDevice)
+            {
+                // デバイスがすでに見つかっている
+                Log.v(TAG, " ALREADY FIND BLUETOOTH DEVICE. : $device.name")
+                return
+            }
+            foundDevice = true
+            val btSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"))
+            val thread = Thread {
+                try
+                {
+                    serialCommunicationMain(btSocket)
+                }
+                catch (e: Exception)
+                {
+                    e.printStackTrace()
+                }
+            }
+            if (btSocket != null)
+            {
+                thread.start()
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/BrainwaveDataHolder.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/BrainwaveDataHolder.kt
new file mode 100644 (file)
index 0000000..9eb53f6
--- /dev/null
@@ -0,0 +1,85 @@
+package jp.osdn.gokigen.thetathoughtshutter.brainwave
+
+import android.util.Log
+import java.util.*
+
+
+class BrainwaveDataHolder(maxBufferSize: Int) : IBrainwaveDataReceiver
+{
+    private val TAG = toString()
+
+    private var valueBuffer: IntArray
+    private var currentSummaryData = BrainwaveSummaryData()
+    private var maxBufferSize = 0
+    private var currentPosition = 0
+    private var bufferIsFull = false
+
+    init
+    {
+        this.maxBufferSize = maxBufferSize
+        valueBuffer = IntArray(maxBufferSize)
+    }
+
+    override fun receivedRawData(value: Int)
+    {
+        //Log.v(TAG, " receivedRawData() : " + value);
+        try {
+            valueBuffer[currentPosition] = value
+            currentPosition++
+            if (currentPosition == maxBufferSize) {
+                currentPosition = 0
+                bufferIsFull = true
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
+
+    override fun receivedSummaryData(data: ByteArray?)
+    {
+        if (data != null)
+        {
+            if (!currentSummaryData.update(data))
+            {
+                // parse failure...
+                Log.v(TAG, " FAIL : PARSE EEG SUMMARY DATA (" + data.size + ")")
+            }
+        }
+    }
+
+    fun getSummaryData(): BrainwaveSummaryData
+    {
+        return currentSummaryData
+    }
+
+    fun getValues(size: Int): IntArray?
+    {
+        var replyData: IntArray? = null
+        try {
+            var endPosition = currentPosition - 1
+            if (currentPosition > size) {
+                return Arrays.copyOfRange(valueBuffer, endPosition - size, endPosition)
+            }
+            if (!bufferIsFull) {
+                return Arrays.copyOfRange(valueBuffer, 0, endPosition)
+            }
+            if (currentPosition == 0) {
+                endPosition = maxBufferSize - 1
+                return Arrays.copyOfRange(valueBuffer, endPosition - size, endPosition)
+            }
+            val remainSize = size - (currentPosition - 1)
+            val size0: IntArray = Arrays.copyOfRange(valueBuffer, 0, currentPosition - 1)
+            val size1: IntArray = Arrays.copyOfRange(
+                valueBuffer,
+                maxBufferSize - 1 - remainSize, maxBufferSize - 1
+            )
+            replyData = IntArray(size)
+            System.arraycopy(size1, 0, replyData, 0, size1.size)
+            System.arraycopy(size0, 0, replyData, size1.size, size0.size)
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        return replyData
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/BrainwaveFileLogger.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/BrainwaveFileLogger.kt
new file mode 100644 (file)
index 0000000..db325b4
--- /dev/null
@@ -0,0 +1,51 @@
+package jp.osdn.gokigen.thetathoughtshutter.brainwave
+
+import android.os.Environment
+import jp.osdn.gokigen.thetathoughtshutter.utils.SimpleLogDumper
+import java.io.File
+import java.io.FileOutputStream
+import java.text.SimpleDateFormat
+import java.util.*
+
+
+class BrainwaveFileLogger(prefix : String = "TTShut", private val loggingSize : Int = 36)
+{
+    private var outputStream: FileOutputStream? = null
+    init
+    {
+        try
+        {
+            val fileNamePrefix = "${prefix}_EEG"
+            val calendar: Calendar = Calendar.getInstance()
+            val extendName: String = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()).format(calendar.time)
+            @Suppress("DEPRECATION") val directoryPath: String = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path.toString() + "/"
+            val outputFileName = fileNamePrefix + "_" + extendName + ".bin"
+            val filepath: String = File(directoryPath.toLowerCase(Locale.getDefault()), outputFileName.toLowerCase(Locale.getDefault())).path
+            outputStream = FileOutputStream(filepath)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    fun outputSummaryData(data: ByteArray)
+    {
+        try
+        {
+            SimpleLogDumper.dumpBytes("RX [" + data.size + "] ", data)
+            if (outputStream != null)
+            {
+                if (data.size >= loggingSize)
+                {
+                    outputStream?.write(data, 0, loggingSize)
+                    outputStream?.flush()
+                }
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/BrainwaveSummaryData.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/BrainwaveSummaryData.kt
new file mode 100644 (file)
index 0000000..6239929
--- /dev/null
@@ -0,0 +1,109 @@
+package jp.osdn.gokigen.thetathoughtshutter.brainwave
+
+import kotlin.experimental.and
+
+class BrainwaveSummaryData
+{
+    //  3-byte value : delta (0.5 - 2.75Hz), theta (3.5 - 6.75Hz), low-alpha (7.5 - 9.25Hz), high-alpha (10 - 11.75Hz), low-beta (13 - 16.75Hz), high-beta (18 - 29.75Hz), low-gamma (31 - 39.75Hz), and mid-gamma (41 - 49.75Hz).
+    private var delta = 0
+    private var theta = 0
+    private var lowAlpha = 0
+    private var highAlpha = 0
+    private var lowBeta = 0
+    private var highBeta = 0
+    private var lowGamma = 0
+    private var midGamma = 0
+    private var poorSignal = 0
+    private var attention = 0
+    private var mediation = 0
+
+    fun update(packet: ByteArray): Boolean
+    {
+        var ret = false
+        try
+        {
+            val length = packet.size
+            if (length < 36)
+            {
+                return ret
+            }
+            poorSignal = packet[4].toInt()
+            delta = (packet[7] and 0xff.toByte()) * 65536 + (packet[8] and 0xff.toByte()) * 256 + (packet[9] and 0xff.toByte())
+            theta = (packet[10] and 0xff.toByte()) * 65536 + (packet[11] and 0xff.toByte()) * 256 + (packet[12] and 0xff.toByte())
+            lowAlpha = (packet[13] and 0xff.toByte()) * 65536 + (packet[14] and 0xff.toByte()) * 256 + (packet[15] and 0xff.toByte())
+            highAlpha = (packet[16] and 0xff.toByte()) * 65536 + (packet[17] and 0xff.toByte()) * 256 + (packet[18] and 0xff.toByte())
+            lowBeta = (packet[19] and 0xff.toByte()) * 65536 + (packet[20] and 0xff.toByte()) * 256 + (packet[21] and 0xff.toByte())
+            highBeta = (packet[22] and 0xff.toByte()) * 65536 + (packet[23] and 0xff.toByte()) * 256 + (packet[24] and 0xff.toByte())
+            lowGamma = (packet[25] and 0xff.toByte()) * 65536 + (packet[26] and 0xff.toByte()) * 256 + (packet[27] and 0xff.toByte())
+            midGamma = (packet[28] and 0xff.toByte()) * 65536 + (packet[29] and 0xff.toByte()) * 256 + (packet[30] and 0xff.toByte())
+            attention = ((packet[32] and 0xff.toByte()).toInt())
+            mediation = ((packet[34] and 0xff.toByte()).toInt())
+            ret = true
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        return ret
+    }
+
+    fun isSkinConnected(): Boolean
+    {
+        return poorSignal != 200
+    }
+
+    fun getPoorSignal(): Int
+    {
+        return poorSignal
+    }
+
+    fun getDelta(): Int
+    {
+        return delta
+    }
+
+    fun getTheta(): Int
+    {
+        return theta
+    }
+
+    fun getLowAlpha(): Int
+    {
+        return lowAlpha
+    }
+
+    fun getHighAlpha(): Int
+    {
+        return highAlpha
+    }
+
+    fun getLowBeta(): Int
+    {
+        return lowBeta
+    }
+
+    fun getHighBeta(): Int
+    {
+        return highBeta
+    }
+
+    fun getLowGamma(): Int
+    {
+        return lowGamma
+    }
+
+    fun getMidGamma(): Int
+    {
+        return midGamma
+    }
+
+    fun getAttention(): Int
+    {
+        return attention
+    }
+
+    fun getMediation(): Int
+    {
+        return mediation
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/IBrainwaveDataReceiver.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/brainwave/IBrainwaveDataReceiver.kt
new file mode 100644 (file)
index 0000000..38b80e5
--- /dev/null
@@ -0,0 +1,7 @@
+package jp.osdn.gokigen.thetathoughtshutter.brainwave
+
+interface IBrainwaveDataReceiver
+{
+    fun receivedRawData(value: Int)
+    fun receivedSummaryData(data: ByteArray?)
+}
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SimpleHttpClient.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SimpleHttpClient.kt
new file mode 100644 (file)
index 0000000..073d3f3
--- /dev/null
@@ -0,0 +1,524 @@
+package jp.osdn.gokigen.thetathoughtshutter.utils
+
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.util.Log
+import java.io.*
+import java.net.HttpURLConnection
+import java.net.URL
+
+class SimpleHttpClient
+{
+    /**
+     *
+     *
+     *
+     */
+    fun httpGet(url: String, timeoutMs: Int): String
+    {
+        var inputStream : InputStream? = null
+        var replyString = ""
+        var timeout = timeoutMs
+        if (timeoutMs < 0)
+        {
+            timeout = DEFAULT_TIMEOUT
+        }
+
+        //  HTTP GETメソッドで要求を投げる
+        try
+        {
+            val httpConn = URL(url).openConnection() as HttpURLConnection
+            try
+            {
+                httpConn.requestMethod = "GET"
+                httpConn.connectTimeout = timeout
+                httpConn.readTimeout = timeout
+                httpConn.connect()
+                val responseCode = httpConn.responseCode
+                if (responseCode == HttpURLConnection.HTTP_OK)
+                {
+                    inputStream = httpConn.inputStream
+                }
+                if (inputStream == null)
+                {
+                    Log.w(TAG, "httpGet: Response Code Error: $responseCode: $url")
+                    return ("")
+                }
+            }
+            catch (ee : Exception)
+            {
+                Log.w(TAG, "httpGet: " + url + "  " + ee.message)
+                ee.printStackTrace()
+                httpConn.disconnect()
+                return ("")
+            }
+        }
+        catch (e: Exception)
+        {
+            Log.w(TAG, "httpGet(2): " + url + "  " + e.message)
+            e.printStackTrace()
+            return ("")
+        }
+
+        // 応答を確認する
+        try
+        {
+            val responseBuf = StringBuilder()
+            val reader = BufferedReader(InputStreamReader(inputStream))
+            var c: Int
+            while (reader.read().also { c = it } != -1)
+            {
+                responseBuf.append(c.toChar())
+            }
+            replyString = responseBuf.toString()
+            reader.close()
+        }
+        catch (e: Exception)
+        {
+            Log.w(TAG, "httpGet: exception: " + e.message)
+            e.printStackTrace()
+        }
+        finally
+        {
+            try
+            {
+                inputStream.close()
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+            }
+        }
+        return (replyString)
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    fun httpGetBytes(url: String, setProperty: Map<String, String>?, timeoutMs: Int, callback: IReceivedMessageCallback)
+    {
+        httpCommandBytes(url, "GET", null, setProperty, null, timeoutMs, callback)
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    fun httpPostBytes(url: String, postData: String?, setProperty: Map<String, String>?, timeoutMs: Int, callback: IReceivedMessageCallback)
+    {
+        httpCommandBytes(url, "POST", postData, setProperty, null, timeoutMs, callback)
+    }
+
+    private fun httpCommandBytes(url: String, requestMethod: String, postData: String?, setProperty: Map<String, String>?, contentType: String?, timeoutMs: Int, callback: IReceivedMessageCallback)
+    {
+        var inputStream: InputStream? = null
+        var timeout = timeoutMs
+        if (timeoutMs < 0)
+        {
+            timeout = DEFAULT_TIMEOUT
+        }
+
+        //  HTTP メソッドで要求を送出
+        try
+        {
+            val httpConn = URL(url).openConnection() as HttpURLConnection
+            httpConn.requestMethod = requestMethod
+            if (setProperty != null)
+            {
+                for (key in setProperty.keys)
+                {
+                    val value = setProperty[key]
+                    httpConn.setRequestProperty(key, value)
+                }
+            }
+            if (contentType != null)
+            {
+                httpConn.setRequestProperty("Content-Type", contentType)
+            }
+            httpConn.connectTimeout = timeout
+            httpConn.readTimeout = timeout
+            if (postData == null)
+            {
+                httpConn.connect()
+            }
+            else
+            {
+                httpConn.doInput = true
+                httpConn.doOutput = true
+                val outputStream = httpConn.outputStream
+                val writer = OutputStreamWriter(outputStream, "UTF-8")
+                writer.write(postData)
+                writer.flush()
+                writer.close()
+                outputStream.close()
+            }
+            val responseCode = httpConn.responseCode
+            if (responseCode == HttpURLConnection.HTTP_OK)
+            {
+                inputStream = httpConn.inputStream
+            }
+            if (inputStream == null)
+            {
+                Log.w(TAG, " http $requestMethod Response Code Error: $responseCode: $url")
+                callback.onErrorOccurred(NullPointerException())
+                callback.onCompleted()
+                return
+            }
+
+            // 応答を確認する
+            try
+            {
+                var contentLength = httpConn.contentLength
+                if (contentLength < 0)
+                {
+                    // コンテンツ長が取れない場合の処理...
+                    try
+                    {
+                        val headers = httpConn.headerFields
+                        // コンテンツ長さが取れない場合は、HTTP応答ヘッダから取得する
+                        val valueList = headers["X-FILE_SIZE"]
+                        try
+                        {
+                            if (valueList != null)
+                            {
+                                contentLength = getValue(valueList).toInt()
+                            }
+                        }
+                        catch (ee: Exception)
+                        {
+                            ee.printStackTrace()
+                        }
+                    }
+                    catch (e: Exception)
+                    {
+                        e.printStackTrace()
+                    }
+                }
+                val buffer = ByteArray(BUFFER_SIZE)
+                var readBytes = 0
+                var readSize = inputStream.read(buffer, 0, BUFFER_SIZE)
+                while (readSize != -1)
+                {
+                    callback.onReceive(readBytes, contentLength, readSize, buffer)
+                    readBytes += readSize
+                    readSize = inputStream.read(buffer, 0, BUFFER_SIZE)
+                }
+                Log.v(TAG, "RECEIVED $readBytes BYTES. (contentLength : $contentLength)")
+                inputStream.close()
+            }
+            catch (e: Exception)
+            {
+                Log.w(TAG, "httpGet: exception: " + e.message)
+                e.printStackTrace()
+                callback.onErrorOccurred(e)
+            }
+            finally
+            {
+                try
+                {
+                    inputStream.close()
+                }
+                catch (e: Exception)
+                {
+                    e.printStackTrace()
+                }
+            }
+        }
+        catch (e: Exception)
+        {
+            Log.w(TAG, "http " + requestMethod + " " + url + "  " + e.message)
+            e.printStackTrace()
+            callback.onErrorOccurred(e)
+            callback.onCompleted()
+            return
+        }
+        callback.onCompleted()
+    }
+
+
+    private fun getValue(valueList: List<String>): String
+    {
+        // 応答ヘッダの値切り出し用...
+        var isFirst = true
+        val values = StringBuilder()
+        for (value in valueList)
+        {
+            values.append(value)
+            if (isFirst)
+            {
+                isFirst = false
+            }
+            else
+            {
+                values.append(" ")
+            }
+        }
+        return values.toString()
+    }
+
+    fun httpGetBitmap(url: String, setProperty: Map<String, String>?, timeoutMs: Int): Bitmap?
+    {
+        return (httpCommandBitmap(url, "GET", null, setProperty, null, timeoutMs))
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    fun httpPostBitmap(url: String, postData: String?, timeoutMs: Int): Bitmap?
+    {
+        return (httpCommandBitmap(url, "POST", postData, null, null, timeoutMs))
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    private fun httpCommandBitmap(url: String, requestMethod: String, postData: String?, setProperty: Map<String, String>?, contentType: String?, timeoutMs: Int): Bitmap?
+    {
+        //var httpConn: HttpURLConnection? = null
+        var inputStream: InputStream? = null
+        //var outputStream: OutputStream? = null
+        //var writer: OutputStreamWriter? = null
+        var bmp: Bitmap? = null
+        var timeout = timeoutMs
+        if (timeoutMs < 0)
+        {
+            timeout = DEFAULT_TIMEOUT
+        }
+
+        //  HTTP メソッドで要求を送出
+        try
+        {
+            val httpConn = URL(url).openConnection() as HttpURLConnection
+            httpConn.requestMethod = requestMethod
+            if (setProperty != null)
+            {
+                for (key in setProperty.keys)
+                {
+                    val value = setProperty[key]
+                    httpConn.setRequestProperty(key, value)
+                }
+            }
+            if (contentType != null)
+            {
+                httpConn.setRequestProperty("Content-Type", contentType)
+            }
+            httpConn.connectTimeout = timeout
+            httpConn.readTimeout = timeout
+            if (postData == null)
+            {
+                httpConn.connect()
+            }
+            else
+            {
+                httpConn.doInput = true
+                httpConn.doOutput = true
+                val outputStream = httpConn.outputStream
+                val writer = OutputStreamWriter(outputStream, "UTF-8")
+                writer.write(postData)
+                writer.flush()
+                writer.close()
+                outputStream.close()
+            }
+            val responseCode = httpConn.responseCode
+            if (responseCode == HttpURLConnection.HTTP_OK)
+            {
+                inputStream = httpConn.inputStream
+                if (inputStream != null)
+                {
+                    bmp = BitmapFactory.decodeStream(inputStream)
+                }
+            }
+            if (inputStream == null)
+            {
+                Log.w(TAG, "http: ($requestMethod) Response Code Error: $responseCode: $url")
+                return (null)
+            }
+            inputStream.close()
+        }
+        catch (e: Exception)
+        {
+            Log.w(TAG, "http: (" + requestMethod + ") " + url + "  " + e.message)
+            e.printStackTrace()
+            return (null)
+        }
+        return (bmp)
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    fun httpPost(url: String, postData: String?, timeoutMs: Int): String?
+    {
+        return (httpCommand(url, "POST", postData, null, null, timeoutMs))
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    fun httpGetWithHeader(url: String, headerMap: Map<String, String>?, contentType: String?, timeoutMs: Int): String?
+    {
+        return (httpCommand(url, "GET", null, headerMap, contentType, timeoutMs))
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    fun httpPostWithHeader(url: String, postData: String?, headerMap: Map<String, String>?, contentType: String?, timeoutMs: Int): String?
+    {
+        return (httpCommand(url, "POST", postData, headerMap, contentType, timeoutMs))
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    fun httpPutWithHeader(url: String, putData: String?, headerMap: Map<String, String>?, contentType: String?, timeoutMs: Int): String?
+    {
+        return (httpCommand(url, "PUT", putData, headerMap, contentType, timeoutMs))
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    fun httpPut(url: String, postData: String?, timeoutMs: Int): String?
+    {
+        return (httpCommand(url, "PUT", postData, null, null, timeoutMs))
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    fun httpOptions(url: String, optionsData: String?, timeoutMs: Int): String?
+    {
+        return (httpCommand(url, "OPTIONS", optionsData, null, null, timeoutMs))
+    }
+
+    /**
+     *
+     *
+     *
+     */
+    private fun httpCommand(url: String, requestMethod: String, postData: String?, setProperty: Map<String, String>?, contentType: String?, timeoutMs: Int): String?
+    {
+        var inputStream: InputStream? = null
+        var timeout = timeoutMs
+        if (timeoutMs < 0)
+        {
+            timeout = DEFAULT_TIMEOUT
+        }
+
+        //  HTTP メソッドで要求を送出
+        try
+        {
+            val httpConn = URL(url).openConnection() as HttpURLConnection
+            httpConn.requestMethod = requestMethod
+            if (setProperty != null)
+            {
+                for (key in setProperty.keys)
+                {
+                    val value = setProperty[key]
+                    httpConn.setRequestProperty(key, value)
+                }
+            }
+            if (contentType != null)
+            {
+                httpConn.setRequestProperty("Content-Type", contentType)
+            }
+            httpConn.connectTimeout = timeout
+            httpConn.readTimeout = timeout
+            if (postData == null)
+            {
+                httpConn.connect()
+            }
+            else
+            {
+                httpConn.doInput = true
+                httpConn.doOutput = true
+                val outputStream = httpConn.outputStream
+                val writer = OutputStreamWriter(outputStream, "UTF-8")
+                writer.write(postData)
+                writer.flush()
+                writer.close()
+                outputStream.close()
+            }
+            val responseCode = httpConn.responseCode
+            if (responseCode == HttpURLConnection.HTTP_OK)
+            {
+                inputStream = httpConn.inputStream
+            }
+            if (inputStream == null)
+            {
+                Log.w(TAG, "http $requestMethod : Response Code Error: $responseCode: $url")
+                return ""
+            }
+        }
+        catch (e: Exception)
+        {
+            Log.w(TAG, "http " + requestMethod + " : IOException: " + e.message)
+            e.printStackTrace()
+            return ("")
+        }
+
+        // 応答の読み出し
+        return readFromInputStream(inputStream)
+    }
+
+    private fun readFromInputStream(inputStream: InputStream?): String
+    {
+        //var reader: BufferedReader? = null
+        var replyString = ""
+        if (inputStream == null)
+        {
+            return ""
+        }
+        try
+        {
+            val responseBuf = StringBuilder()
+            val reader = BufferedReader(InputStreamReader(inputStream))
+            var c: Int
+            while (reader.read().also { c = it } != -1)
+            {
+                responseBuf.append(c.toChar())
+            }
+            replyString = responseBuf.toString()
+            reader.close()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        return replyString
+    }
+
+    interface IReceivedMessageCallback
+    {
+        fun onCompleted()
+        fun onErrorOccurred(e: Exception?)
+        fun onReceive(readBytes: Int, length: Int, size: Int, data: ByteArray?)
+    }
+
+    companion object
+    {
+        private val TAG = SimpleHttpClient::class.java.simpleName
+        private const val DEFAULT_TIMEOUT = 10 * 1000 // [ms]
+        private const val BUFFER_SIZE = 131072 * 2 // 256kB
+    }
+}
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SimpleLiveViewSlicer.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SimpleLiveViewSlicer.kt
new file mode 100644 (file)
index 0000000..a1be8a9
--- /dev/null
@@ -0,0 +1,351 @@
+package jp.osdn.gokigen.thetathoughtshutter.utils
+
+import android.util.Log
+import java.io.*
+import java.net.HttpURLConnection
+import java.net.URL
+
+class SimpleLiveViewSlicer
+{
+    class Payload(private val jpegData: ByteArray?, val paddingData: ByteArray?)
+    {
+        fun getJpegData(): ByteArray?
+        {
+            return jpegData
+        }
+    }
+
+    private var mJpegStartMarker = intArrayOf(0x0d, 0x0a, 0x0d, 0x0a, 0xff, 0xd8)
+    private var mHttpConn: HttpURLConnection? = null
+    private var mInputStream : InputStream? = null
+
+    fun setMJpegStartMarker(startMarker: IntArray)
+    {
+        mJpegStartMarker = startMarker
+    }
+
+    fun open(liveViewUrl: String?, postData: String?, contentType: String?)
+    {
+        try
+        {
+            if ((mInputStream != null)||(mHttpConn != null))
+            {
+                Log.v(TAG, "Slicer is already open.")
+                return
+            }
+            val urlObj = URL(liveViewUrl)
+            mHttpConn = urlObj.openConnection() as HttpURLConnection
+            mHttpConn?.requestMethod = "POST"
+            mHttpConn?.connectTimeout = CONNECTION_TIMEOUT
+            if (contentType != null)
+            {
+                mHttpConn?.setRequestProperty("Content-Type", contentType)
+            }
+            run {
+                try
+                {
+                    mHttpConn?.doInput = true
+                    mHttpConn?.doOutput = true
+                    val outputStream = mHttpConn?.outputStream
+                    val writer = OutputStreamWriter(outputStream, "UTF-8")
+                    writer.write(postData)
+                    writer.flush()
+                    writer.close()
+                    outputStream?.close()
+                }
+                catch (e : Exception)
+                {
+                    e.printStackTrace()
+                }
+            }
+            if (mHttpConn?.responseCode == HttpURLConnection.HTTP_OK)
+            {
+                mInputStream = mHttpConn?.inputStream
+            }
+            else
+            {
+                Log.v(TAG, " RESPONSE NG : " + mHttpConn?.responseCode + " " + mHttpConn?.responseMessage)
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    fun open(liveViewUrl: String?)
+    {
+        try
+        {
+            if ((mInputStream != null)||(mHttpConn != null))
+            {
+                Log.v(TAG, "Slicer is already open.")
+                return
+            }
+            val urlObj = URL(liveViewUrl)
+            mHttpConn = urlObj.openConnection() as HttpURLConnection
+            mHttpConn?.requestMethod = "GET"
+            mHttpConn?.connectTimeout = CONNECTION_TIMEOUT
+            mHttpConn?.connect()
+            if (mHttpConn?.responseCode == HttpURLConnection.HTTP_OK)
+            {
+                mInputStream = mHttpConn?.inputStream
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    fun close()
+    {
+        try
+        {
+            if (mInputStream != null)
+            {
+                mInputStream?.close()
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        try
+        {
+            if (mHttpConn != null)
+            {
+                mHttpConn?.disconnect()
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    fun nextPayload(): Payload?
+    {
+        var payload: Payload? = null
+        try
+        {
+            while ((mInputStream != null)&&(payload == null))
+            {
+                // Common Header
+                var readLength = 1 + 1 + 2 + 4
+                val commonHeader = readBytes(mInputStream!!, readLength)
+                if ((commonHeader == null)||(commonHeader.size != readLength))
+                {
+                    Log.v(TAG, "Cannot read stream for common header.")
+                    payload = null
+                    break
+                }
+                if (commonHeader[0].toUByte().toByte() != 0xFF.toByte())
+                {
+                    Log.v(TAG, "Unexpected data format. (Start byte)")
+                    payload = null
+                    break
+                }
+                when (commonHeader[1].toUByte().toByte())
+                {
+                    0x12.toByte() -> {
+                        // This is information header for streaming. skip this packet.
+                        readLength = 4 + 3 + 1 + 2 + 118 + 4 + 4 + 24
+                        readBytes(mInputStream!!, readLength)
+                    }
+                    0x01.toByte(), 0x11.toByte() -> payload = readPayload()
+                    else -> {
+                    }
+                }
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+            System.gc()
+        }
+        return payload
+    }
+
+    private fun readPayload(): Payload?
+    {
+        try
+        {
+            if (mInputStream != null)
+            {
+                // Payload Header
+                val readLength = 4 + 3 + 1 + 4 + 1 + 115
+                val payloadHeader = readBytes(mInputStream!!, readLength)
+                if (payloadHeader == null || payloadHeader.size != readLength)
+                {
+                    throw EOFException("Cannot read stream for payload header.")
+                }
+                if (payloadHeader[0].toUByte().toByte() != 0x24.toByte() || payloadHeader[1].toUByte().toByte() != 0x35.toByte() || payloadHeader[2].toUByte().toByte() != 0x68.toByte() || payloadHeader[3].toUByte().toByte() != 0x79.toByte())
+                {
+                    throw EOFException("Unexpected data format. (Start code)")
+                }
+                val jpegSize = bytesToInt(payloadHeader, 4, 3)
+                val paddingSize = bytesToInt(payloadHeader, 7, 1)
+
+                // Payload Data
+                val jpegData = readBytes(mInputStream!!, jpegSize)
+                val paddingData = readBytes(mInputStream!!, paddingSize)
+                return Payload(jpegData, paddingData)
+            }
+        }
+        catch (eo: EOFException)
+        {
+            eo.printStackTrace()
+            close()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        return (null)
+    }
+
+    /**
+     * 先頭のjpegマーカーが出てくるまで読み飛ばす
+     *
+     */
+    private fun skipJpegMarkStart(stream: InputStream?)
+    {
+        if (stream == null)
+        {
+            return
+        }
+        var searchIndex = 0
+        while (true)
+        {
+            try
+            {
+                val data = stream.read()
+                if (data == mJpegStartMarker[searchIndex])
+                {
+                    searchIndex++
+                    if (searchIndex >= mJpegStartMarker.size)
+                    {
+                        break
+                    }
+                }
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+                return
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    fun nextPayloadForMotionJpeg(): Payload?
+    {
+        var searchIndex = 0
+        val endMarker = intArrayOf(0xff, 0xd9)
+        var payload: Payload? = null
+        try
+        {
+            while ((mInputStream != null)&&(payload == null))
+            {
+                skipJpegMarkStart(mInputStream)
+                val tmpByteArray = ByteArrayOutputStream()
+                // 先頭にJPEGのマークを詰める
+                tmpByteArray.write(0xff)
+                tmpByteArray.write(0xd8)
+                while (true)
+                {
+                    try
+                    {
+                        // 1byteづつの読み込み... 本当は複数バイト読み出しで処理したい
+                        val data = mInputStream?.read()
+                        if (data != null)
+                        {
+                            tmpByteArray.write(data)
+                            if (data == endMarker[searchIndex])
+                            {
+                                searchIndex++
+                                if (searchIndex >= endMarker.size)
+                                {
+                                    break
+                                }
+                            }
+                            else
+                            {
+                                searchIndex = 0
+                            }
+                        }
+                    }
+                    catch (e: Throwable)
+                    {
+                        Log.v(TAG, "INPUT STREAM EXCEPTION : " + e.localizedMessage)
+                        return (null)
+                    }
+                }
+                payload = Payload(tmpByteArray.toByteArray(), null)
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        return (payload)
+    }
+
+    companion object
+    {
+        private val TAG = SimpleLiveViewSlicer::class.java.simpleName
+        private const val CONNECTION_TIMEOUT = 2000 // [msec]
+        private fun bytesToInt(byteData: ByteArray, startIndex: Int, count: Int): Int
+        {
+            var ret = 0
+            try
+            {
+                for (i in startIndex until startIndex + count)
+                {
+                    ret = ret shl 8 or (byteData[i].toUByte().toInt() and 0xff)
+                }
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+            }
+            return ret
+        }
+
+        private fun readBytes(inputStream: InputStream, length: Int): ByteArray?
+        {
+            var ret: ByteArray?
+            try
+            {
+                val tmpByteArray = ByteArrayOutputStream()
+                val buffer = ByteArray(1024)
+                while (true)
+                {
+                    val trialReadlen = Math.min(buffer.size, length - tmpByteArray.size())
+                    val readlen = inputStream.read(buffer, 0, trialReadlen)
+                    if (readlen < 0)
+                    {
+                        break
+                    }
+                    tmpByteArray.write(buffer, 0, readlen)
+                    if (length <= tmpByteArray.size())
+                    {
+                        break
+                    }
+                }
+                ret = tmpByteArray.toByteArray()
+                tmpByteArray.close()
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+                ret = null
+            }
+            return ret
+        }
+    }
+}
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SimpleLogDumper.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SimpleLogDumper.kt
new file mode 100644 (file)
index 0000000..a7da76d
--- /dev/null
@@ -0,0 +1,72 @@
+package jp.osdn.gokigen.thetathoughtshutter.utils
+
+import android.app.Activity
+import android.os.Environment
+import android.util.Log
+import jp.osdn.gokigen.thetathoughtshutter.R
+import java.io.File
+import java.io.FileOutputStream
+import java.text.SimpleDateFormat
+import java.util.*
+
+object SimpleLogDumper
+{
+    private val TAG = SimpleLogDumper::class.java.simpleName
+
+    /**
+     * デバッグ用:ログにバイト列を出力する
+     *
+     */
+    fun dumpBytes(header: String, data: ByteArray?)
+    {
+        if (data == null)
+        {
+            Log.v(TAG, "DATA IS NULL")
+            return
+        }
+        if (data.size > 8192)
+        {
+            Log.v(TAG, " --- DUMP DATA IS TOO LONG... " + data.size + " bytes.")
+            return
+        }
+        var index = 0
+        var message: StringBuffer
+        message = StringBuffer()
+        for (item in data)
+        {
+            index++
+            message.append(String.format("%02x ", item))
+            if (index >= 16)
+            {
+                Log.v(TAG, "$header $message")
+                index = 0
+                message = StringBuffer()
+            }
+        }
+        if (index != 0)
+        {
+            Log.v(TAG, "$header $message")
+        }
+        System.gc()
+    }
+
+    fun binaryOutputToFile(activity: Activity, fileNamePrefix: String, rx_body: ByteArray)
+    {
+        try
+        {
+            val calendar = Calendar.getInstance()
+            val extendName = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()).format(calendar.time)
+            @Suppress("DEPRECATION") val directoryPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path + "/" + activity.getString(R.string.app_name2) + "/"
+            val outputFileName = fileNamePrefix + "_" + extendName + ".bin"
+            val filepath = File(directoryPath.toLowerCase(Locale.ROOT), outputFileName.toLowerCase(Locale.ROOT)).path
+            val outputStream = FileOutputStream(filepath)
+            outputStream.write(rx_body, 0, rx_body.size)
+            outputStream.flush()
+            outputStream.close()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+}
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SnackBarMessage.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/SnackBarMessage.kt
new file mode 100644 (file)
index 0000000..5624b17
--- /dev/null
@@ -0,0 +1,54 @@
+package jp.osdn.gokigen.thetathoughtshutter.utils
+
+import android.app.Activity
+import android.util.Log
+import android.widget.Toast
+import com.google.android.material.snackbar.Snackbar
+import jp.osdn.gokigen.thetathoughtshutter.R
+
+class SnackBarMessage(private val context: Activity, private val isToast: Boolean)
+{
+    private val TAG = toString()
+    fun showMessage(message: String?)
+    {
+        try
+        {
+            Log.v(TAG, message!!)
+            context.runOnUiThread {
+                try
+                {
+                    if (!isToast)
+                    {
+                        // Snackbarでメッセージを通知する
+                        Snackbar.make(context.findViewById(R.id.main_layout), message, Snackbar.LENGTH_LONG).show()
+                    }
+                    else
+                    {
+                        // Toastでメッセージを通知する
+                        Toast.makeText(context, message, Toast.LENGTH_LONG).show()
+                    }
+                }
+                catch (e: Exception)
+                {
+                    e.printStackTrace()
+                }
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    fun showMessage(stringId: Int)
+    {
+        try
+        {
+            showMessage(context.getString(stringId))
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+}
diff --git a/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/XmlElement.kt b/app/src/main/java/jp/osdn/gokigen/thetathoughtshutter/utils/XmlElement.kt
new file mode 100644 (file)
index 0000000..a985e5f
--- /dev/null
@@ -0,0 +1,158 @@
+package jp.osdn.gokigen.thetathoughtshutter.utils
+
+import android.util.Log
+import android.util.Xml
+import org.xmlpull.v1.XmlPullParser
+import java.io.StringReader
+import java.util.*
+
+class XmlElement private constructor()
+{
+    //Log.v(TAG, "XmlElement Tag [" + tagName + "]");
+    var tagName = ""
+        get() =//Log.v(TAG, "XmlElement Tag [" + tagName + "]");
+            field
+        private set
+    private var tagValue: String
+    private val childElements: LinkedList<XmlElement> = LinkedList()
+    private val attributes: MutableMap<String, String>
+    private var parentElement: XmlElement? = null
+
+    val parent: XmlElement?
+        get() = parentElement
+
+    //Log.v(TAG, "XmlElement Value [" + tagValue + "]");
+    var value: String
+        get() =//Log.v(TAG, "XmlElement Value [" + tagValue + "]");
+            tagValue
+        private set(value)
+        {
+            tagValue = value
+        }
+
+    private fun putChild(childItem: XmlElement)
+    {
+        childElements.add(childItem)
+        childItem.setParent(this)
+    }
+
+    fun findChild(name: String): XmlElement
+    {
+        for (child in childElements)
+        {
+            if (child.tagName == name)
+                return child
+        }
+        return XmlElement()
+    }
+
+    fun findChildren(name: String): List<XmlElement>
+    {
+        val tagItemList: MutableList<XmlElement> = ArrayList()
+        for (child in childElements)
+        {
+            if (child.tagName == name)
+            {
+                tagItemList.add(child)
+            }
+        }
+        return tagItemList
+    }
+
+    private fun setParent(parent: XmlElement)
+    {
+        parentElement = parent
+    }
+
+    private fun putAttribute(name: String, value: String)
+    {
+        attributes[name] = value
+    }
+
+    fun getAttribute(name: String, defaultValue: String?): String?
+    {
+        var ret = attributes[name]
+        if (ret == null)
+        {
+            ret = defaultValue
+        }
+        return ret
+    }
+
+    companion object
+    {
+        private val TAG = XmlElement::class.java.simpleName
+        private val NULL_ELEMENT = XmlElement()
+        private fun parse(xmlPullParser: XmlPullParser): XmlElement
+        {
+            var rootElement = NULL_ELEMENT
+            try
+            {
+                var parsingElement: XmlElement? = NULL_ELEMENT
+                MAINLOOP@ while (true) {
+                    when (xmlPullParser.next())
+                    {
+                        XmlPullParser.START_DOCUMENT -> Log.v(
+                            TAG, "------- START DOCUMENT -----"
+                        )
+                        XmlPullParser.START_TAG -> {
+                            val childItem = XmlElement()
+                            childItem.tagName = xmlPullParser.name
+                            if (parsingElement === NULL_ELEMENT) {
+                                rootElement = childItem
+                            } else {
+                                parsingElement!!.putChild(childItem)
+                            }
+                            parsingElement = childItem
+
+                            // Set Attribute
+                            var i = 0
+                            while (i < xmlPullParser.attributeCount) {
+                                parsingElement.putAttribute(
+                                    xmlPullParser.getAttributeName(i),
+                                    xmlPullParser.getAttributeValue(i)
+                                )
+                                i++
+                            }
+                        }
+                        XmlPullParser.TEXT -> parsingElement!!.value = xmlPullParser.text
+                        XmlPullParser.END_TAG -> parsingElement = parsingElement!!.parent
+                        XmlPullParser.END_DOCUMENT -> {
+                            Log.v(TAG, "------- END DOCUMENT -------")
+                            break@MAINLOOP
+                        }
+                        else -> break@MAINLOOP
+                    }
+                }
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+                rootElement = NULL_ELEMENT
+            }
+            return rootElement
+        }
+
+        fun parse(xmlStr: String): XmlElement
+        {
+            try
+            {
+                val xmlPullParser = Xml.newPullParser()
+                xmlPullParser.setInput(StringReader(xmlStr))
+                return parse(xmlPullParser)
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+            }
+            return XmlElement()
+        }
+    }
+
+    init
+    {
+        //Log.v(TAG, "XmlElement()");
+        attributes = HashMap()
+        tagValue = ""
+    }
+}
index 69ec788..ebc337b 100644 (file)
@@ -1,4 +1,10 @@
 <resources>
     <string name="app_name">ThetaThoughtShutter</string>
+    <string name="app_name2">TTShut</string>
     <string name="blank"> </string>
+    <string name="bluetooth_setting_is_off">Bluetooth Setting is OFF</string>
+    <string name="not_support_bluetooth">Does not support a Bluetooth.</string>
+    <string name="bluetooth_scan_start_failure">Bluetooth scan start failure.</string>
+    <string name="scan_fail_via_bluetooth">Bluetooth scan failure.</string>
+    <string name="bluetooth_scan_finished">Bluetooth scan finished.</string>
 </resources>
\ No newline at end of file