OSDN Git Service

VisionKids への対応準備。
authorMRSa <mrsa@myad.jp>
Sun, 23 Jul 2023 15:04:31 +0000 (00:04 +0900)
committerMRSa <mrsa@myad.jp>
Sun, 23 Jul 2023 15:04:31 +0000 (00:04 +0900)
30 files changed:
app/src/main/java/net/osdn/gokigen/pkremote/camera/CameraInterfaceProvider.java
app/src/main/java/net/osdn/gokigen/pkremote/camera/interfaces/control/ICameraConnection.java
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/IVisionKidsInterfaceProvider.kt [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/FtpCommand.kt [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/IFtpServiceCallback.kt [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/MyFtpClient.kt [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/VisionKidsInterfaceProvider.kt [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsCameraConnectSequence.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsCameraDisconnectSequence.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsCameraPowerOff.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsConnection.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/playback/VisionKidsPlaybackControl.kt [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/preference/IPreferencePropertyAccessor.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/canon/CanonPreferenceFragment.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/fujix/FujiXPreferenceFragment.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/nikon/NikonPreferenceFragment.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/olympus/OpcPreferenceFragment.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/olympuspen/OlympusPenPreferenceFragment.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/panasonic/PanasonicPreferenceFragment.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/pixpro/PixproPreferenceFragment.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/ricohgr2/RicohGr2PreferenceFragment.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/sony/SonyPreferenceFragment.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/theta/ThetaPreferenceFragment.java
app/src/main/java/net/osdn/gokigen/pkremote/preference/visionkids/VisionKidsPreferenceFragment.kt [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/scene/CameraSceneUpdater.java
app/src/main/res/values-ja/arrays.xml
app/src/main/res/values-ja/strings.xml
app/src/main/res/values/arrays.xml
app/src/main/res/values/strings.xml
app/src/main/res/xml/preferences_visionkids.xml [new file with mode: 0644]

index da03f1a..4fe5d77 100644 (file)
@@ -37,6 +37,7 @@ import net.osdn.gokigen.pkremote.camera.vendor.ricoh.wrapper.RicohGr2InterfacePr
 import net.osdn.gokigen.pkremote.camera.vendor.sony.ISonyInterfaceProvider;
 import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.SonyCameraWrapper;
 import net.osdn.gokigen.pkremote.camera.vendor.theta.wrapper.ThetaInterfaceProvider;
+import net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper.VisionKidsInterfaceProvider;
 import net.osdn.gokigen.pkremote.preference.IPreferencePropertyAccessor;
 
 import androidx.annotation.NonNull;
@@ -59,6 +60,8 @@ public class CameraInterfaceProvider implements IInterfaceProvider
     private final OlympusPenInterfaceProvider olympuspen;
     private final ThetaInterfaceProvider theta;
     private final PixproInterfaceProvider pixpro;
+    private final VisionKidsInterfaceProvider visionKids;
+
     private final IInformationReceiver informationReceiver;
     private final CameraContentsRecognizer cameraContentsRecognizer;
     private final AppCompatActivity context;
@@ -88,6 +91,7 @@ public class CameraInterfaceProvider implements IInterfaceProvider
         olympuspen = new OlympusPenInterfaceProvider(context, provider);
         theta = new ThetaInterfaceProvider(context, provider);
         pixpro = new PixproInterfaceProvider(context, provider, informationReceiver);
+        visionKids = new VisionKidsInterfaceProvider(context, provider, informationReceiver);
         this.informationReceiver = informationReceiver;
         this.cameraContentsRecognizer = new CameraContentsRecognizer(context, this);
     }
@@ -163,6 +167,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getPixproCameraConnection());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getVisionKidsCameraConnection());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getRicohGr2CameraConnection());
@@ -217,6 +225,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getButtonControl());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getButtonControl());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getButtonControl());
@@ -271,6 +283,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getDisplayInjector());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getDisplayInjector());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getDisplayInjector());
@@ -325,6 +341,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getLiveViewControl());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getLiveViewControl());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getLiveViewControl());
@@ -379,6 +399,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getLiveViewListener());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getLiveViewListener());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getLiveViewListener());
@@ -433,6 +457,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getFocusingControl());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getFocusingControl());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getFocusingControl());
@@ -487,6 +515,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getCameraInformation());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getCameraInformation());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getCameraInformation());
@@ -541,6 +573,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getZoomLensControl());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getZoomLensControl());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getZoomLensControl());
@@ -595,6 +631,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getCaptureControl());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getCaptureControl());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getCaptureControl());
@@ -649,6 +689,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getCameraStatusListHolder());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getCameraStatusListHolder());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getCameraStatusListHolder());
@@ -703,6 +747,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getCameraStatusWatcher());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getCameraStatusWatcher());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getCameraStatusWatcher());
@@ -757,6 +805,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getPlaybackControl());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getPlaybackControl());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getPlaybackControl());
@@ -811,6 +863,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getHardwareStatus());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getHardwareStatus());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getHardwareStatus());
@@ -865,6 +921,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 return (pixpro.getCameraRunMode());
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+            {
+                return (visionKids.getCameraRunMode());
+            }
             else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH)
             {
                 return (ricohGr2.getCameraRunMode());
@@ -964,6 +1024,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 ret = ICameraConnection.CameraConnectionMethod.PIXPRO;
             }
+            else if (connectionMethod.contains("VISIONKIDS"))
+            {
+                ret = ICameraConnection.CameraConnectionMethod.VISIONKIDS;
+            }
             else // if (connectionMethod.contains("OPC"))
             {
                 ret = ICameraConnection.CameraConnectionMethod.OPC;
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/IVisionKidsInterfaceProvider.kt b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/IVisionKidsInterfaceProvider.kt
new file mode 100644 (file)
index 0000000..23d662e
--- /dev/null
@@ -0,0 +1,36 @@
+package net.osdn.gokigen.pkremote.camera.vendor.visionkids
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraButtonControl
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraRunMode
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICaptureControl
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingControl
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IZoomLensControl
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IDisplayInjector
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewControl
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewListener
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.IPlaybackControl
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraHardwareStatus
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraInformation
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatus
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusWatcher
+
+interface IVisionKidsInterfaceProvider
+{
+    fun getVisionKidsCameraConnection(): ICameraConnection?
+    fun getLiveViewControl(): ILiveViewControl?
+    fun getLiveViewListener(): ILiveViewListener?
+    fun getFocusingControl(): IFocusingControl?
+    fun getCameraInformation(): ICameraInformation?
+    fun getZoomLensControl(): IZoomLensControl?
+    fun getCaptureControl(): ICaptureControl?
+    fun getDisplayInjector(): IDisplayInjector?
+    fun getCameraStatusListHolder(): ICameraStatus?
+    fun getButtonControl(): ICameraButtonControl?
+    fun getCameraStatusWatcher(): ICameraStatusWatcher?
+    fun getPlaybackControl(): IPlaybackControl?
+
+    fun getHardwareStatus(): ICameraHardwareStatus?
+    fun getCameraRunMode(): ICameraRunMode?
+
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/FtpCommand.kt b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/FtpCommand.kt
new file mode 100644 (file)
index 0000000..09df07f
--- /dev/null
@@ -0,0 +1,3 @@
+package net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper
+
+data class FtpCommand(val command: String, val value: String)
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/IFtpServiceCallback.kt b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/IFtpServiceCallback.kt
new file mode 100644 (file)
index 0000000..103f194
--- /dev/null
@@ -0,0 +1,6 @@
+package net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper
+
+interface IFtpServiceCallback
+{
+    fun onReceivedFtpResponse(command: String, code: Int, response: String)
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/MyFtpClient.kt b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/MyFtpClient.kt
new file mode 100644 (file)
index 0000000..7e01b8f
--- /dev/null
@@ -0,0 +1,415 @@
+package net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper
+
+import android.util.Log
+import net.osdn.gokigen.pkremote.camera.utils.SimpleLogDumper
+import java.io.ByteArrayOutputStream
+import java.io.DataOutputStream
+import java.io.InputStream
+import java.lang.Exception
+import java.net.InetSocketAddress
+import java.net.Socket
+import java.util.ArrayDeque
+import java.util.Queue
+
+class MyFtpClient(private val callbackReceiver: IFtpServiceCallback, private val isDumpReceiveLog: Boolean = false)
+{
+    private var isStart = false
+    private var isConnected = false
+    private var socket: Socket? = null
+    private var dataOutputStream: DataOutputStream? = null
+    //private var bufferedReader: BufferedReader? = null
+    private var connectedAddress : String? = null
+    private val commandQueue : Queue<FtpCommand> = ArrayDeque()
+
+    private var isStartDataPort = false
+    private var isConnectedDataPort = false
+    private var socketDataPort: Socket? = null
+    private var dataOutputStreamDataPort: DataOutputStream? = null
+
+    fun connect(address: String)
+    {
+        try
+        {
+            connectedAddress = address
+            Log.v(TAG, "connect to $address")
+            val thread = Thread {
+                try
+                {
+                    val tcpNoDelay = true
+                    Log.v(TAG, " connect() : $address")
+                    socket = Socket()
+                    socket?.reuseAddress = true
+                    socket?.keepAlive = true
+                    socket?.tcpNoDelay = true
+                    if (tcpNoDelay)
+                    {
+                        socket?.keepAlive = false
+                        socket?.setPerformancePreferences(0, 2, 0)
+                        socket?.oobInline = true
+                        socket?.reuseAddress = false
+                        socket?.trafficClass = 0x80
+                    }
+                    socket?.connect(InetSocketAddress(address, FTP_CONTROL_PORT), 0)
+                    dataOutputStream = DataOutputStream(socket?.getOutputStream())
+                    isConnected = true
+
+                    // 接続後の一発目は、自動で読み込んでみる
+                    val connectCommand = FtpCommand("connect", "connect")
+                    receiveFromDevice(connectCommand, socket, DATA_POLL_QUEUE_MS, MAX_RETRY_WAIT_COUNT)
+                    sendCommandMain()
+                }
+                catch (e: Exception)
+                {
+                    e.printStackTrace()
+                    callbackReceiver.onReceivedFtpResponse("connect", -1, e.message?:"EXCEPTION")
+                }
+            }
+            thread.start()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    fun enqueueCommand(command: FtpCommand): Boolean
+    {
+        try
+        {
+            Log.v(TAG, " Command Enqueue : ${command.command} : ${command.value}")
+            return commandQueue.offer(command)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        return (false)
+    }
+
+    fun disconnect()
+    {
+        Log.v(TAG, "  ----- DISCONNECT -----")
+        try
+        {
+            // 通信関連のクローズ
+            closeOutputStream()
+            closeSocket()
+            isStart = false
+            isStartDataPort = false
+            isConnected = false
+            isConnectedDataPort = false
+            commandQueue.clear()
+            connectedAddress = null
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        System.gc()
+    }
+
+    fun decidePassivePort(response: String)
+    {
+        try
+        {
+            // データポートの IPアドレスとポート番号を応答データから切り出す
+            val pickupString = response.substring((response.indexOf("(") + 1), response.indexOf(")"))
+            val dataStringArray = pickupString.split(",")
+            val dataPortAddress = dataStringArray[0] + "." + dataStringArray[1] + "." +  dataStringArray[2] + "." +  dataStringArray[3]
+            val dataPort = dataStringArray[4].toInt() * 256 + dataStringArray[5].toInt()
+            val passiveAddress = "$dataPortAddress:$dataPort:\r\n"
+            Log.v(TAG, " - - - - - -  data Port : $passiveAddress ($pickupString  ${dataStringArray.size})")
+            callbackReceiver.onReceivedFtpResponse("data_port", 0, passiveAddress)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        sleep(RECEIVE_WAIT_MS)
+    }
+
+    fun openPassivePort(address: String): Boolean
+    {
+        var response = true
+        try
+        {
+            // データポートをオープンして受信できるようにする
+            val accessPoint = address.split(":")
+            Log.v(TAG, "openPassivePort address:$connectedAddress (or ${accessPoint[0]}) port:${accessPoint[1]}")
+            val thread = Thread {
+                try
+                {
+                    val tcpNoDelay = true
+                    Log.v(TAG, " connect() : address:${accessPoint[0]} port:${accessPoint[1]}")
+                    socketDataPort = Socket()
+                    socketDataPort?.reuseAddress = true
+                    socketDataPort?.keepAlive = true
+                    socketDataPort?.tcpNoDelay = true
+                    if (tcpNoDelay)
+                    {
+                        socketDataPort?.keepAlive = false
+                        socketDataPort?.setPerformancePreferences(0, 2, 0)
+                        socketDataPort?.oobInline = true
+                        socketDataPort?.reuseAddress = false
+                        socketDataPort?.trafficClass = 0x80
+                    }
+                    val dataAddress = if (connectedAddress != null) { connectedAddress } else { accessPoint[0] }
+                    socketDataPort?.connect(InetSocketAddress(dataAddress, accessPoint[1].toInt()), 0)
+                    dataOutputStreamDataPort = DataOutputStream(socketDataPort?.getOutputStream())
+                    isConnectedDataPort = true
+                    receiveDataMain()
+                }
+                catch (e: Exception)
+                {
+                    e.printStackTrace()
+                    callbackReceiver.onReceivedFtpResponse("passive_data", -1, e.message?:"EXCEPTION")
+                }
+            }
+            thread.start()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+            response = false
+        }
+        return (response)
+    }
+
+    private fun closeOutputStream()
+    {
+        try
+        {
+            dataOutputStream?.close()
+            dataOutputStreamDataPort?.close()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        dataOutputStream = null
+        dataOutputStreamDataPort = null
+    }
+
+    private fun closeSocket()
+    {
+        try
+        {
+            socket?.close()
+            socketDataPort?.close()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        socket = null
+        socketDataPort = null
+    }
+
+    private fun receiveDataMain()
+    {
+        if (isStartDataPort)
+        {
+            // すでにコマンドのスレッド動作中なので抜ける
+            return
+        }
+        isStartDataPort = true
+        Log.v(TAG, " receiveDataMain() : START")
+        val command = FtpCommand("data", " \r\n")
+        while (isStartDataPort)
+        {
+            try
+            {
+                Log.v(TAG, " --- RECEIVE DATA STANDBY --- ")
+                sleep(DATA_POLL_QUEUE_MS)
+                receiveFromDevice(command, socketDataPort, DATA_POLL_QUEUE_MS, MAX_RETRY_WAIT_COUNT_DATA)
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+                callbackReceiver.onReceivedFtpResponse("receiveDataMain", -1, e.message?:"EXCEPTION")
+            }
+        }
+    }
+
+    private fun sendCommandMain()
+    {
+        if (isStart)
+        {
+            // すでにコマンドのスレッド動作中なので抜ける
+            return
+        }
+        isStart = true
+        Log.v(TAG, " sendCommandMain() : START")
+        while (isStart)
+        {
+            try
+            {
+                val command = commandQueue.poll()
+                if (command != null)
+                {
+                    issueCommand(command)
+                    sleep(COMMAND_POLL_QUEUE_MS)
+
+                    Log.v(TAG, " --- RECEIVE WAIT FOR REPLY --- ")
+                    receiveFromDevice(command, socket, COMMAND_POLL_QUEUE_MS, MAX_RETRY_WAIT_COUNT)
+                }
+                sleep(COMMAND_POLL_QUEUE_MS)
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+                callbackReceiver.onReceivedFtpResponse("sendCommandMain", -1, e.message?:"EXCEPTION")
+            }
+        }
+    }
+
+    private fun receiveFromDevice(command: FtpCommand, targetSocket: Socket?, wait: Int, maxRetry : Int)
+    {
+        try
+        {
+            val byteArray = ByteArray(PACKET_BUFFER_SIZE)
+            val inputStream: InputStream? = targetSocket?.getInputStream()
+            if (inputStream == null)
+            {
+                Log.v(TAG, " InputStream is NULL... RECEIVE ABORTED.")
+                callbackReceiver.onReceivedFtpResponse("receiveFromDevice($command)", -1, "InputStream is NULL...")
+                return
+            }
+
+            // 初回データが受信バッファにデータが溜まるまで待つ...
+            var readBytes = waitForReceive(inputStream, wait, maxRetry)
+            if (readBytes < 0)
+            {
+                // リトライオーバー検出
+                Log.v(TAG, "  ----- DETECT RECEIVE RETRY OVER... -----")
+                callbackReceiver.onReceivedFtpResponse("receiveFromDevice(${command.command})", -1, "Receive timeout (${COMMAND_POLL_QUEUE_MS * MAX_RETRY_WAIT_COUNT} ms)")
+            }
+
+            // 受信したデータをバッファに突っ込む
+            var isWriteData = false
+            //var dataValue = ""
+            val byteStream = ByteArrayOutputStream()
+            byteStream.reset()
+            while (readBytes > 0)
+            {
+                readBytes = inputStream.read(byteArray, 0, PACKET_BUFFER_SIZE)
+                if (readBytes <= 0)
+                {
+                    Log.v(TAG," RECEIVED MESSAGE FINISHED ($readBytes)")
+                    break
+                }
+                //Log.v(TAG, " :::::::::: [${command.command}] Read Bytes: $readBytes")
+                byteStream.write(byteArray, 0, readBytes)
+                //dataValue += String(byteArray.copyOfRange(0, readBytes))
+                isWriteData = true
+                sleep(RECEIVE_WAIT_MS)
+                readBytes = inputStream.available()
+            }
+            if (isWriteData)
+            {
+                //Log.v(TAG, " >>>>[${command.command}]>>>>>> $dataValue")
+                callbackReceiver.onReceivedFtpResponse(command.command, 0, String(byteStream.toByteArray()))
+                //callbackReceiver.onReceivedFtpResponse(command.command, 0, dataValue)
+            }
+            System.gc()
+        }
+        catch (t: Throwable)
+        {
+            t.printStackTrace()
+            callbackReceiver.onReceivedFtpResponse("receiveFromDevice", -1, t.message?:"EXCEPTION")
+        }
+    }
+
+    private fun issueCommand(command: FtpCommand)
+    {
+        try
+        {
+            val byteArray = command.value.toByteArray()
+            if (byteArray.isEmpty())
+            {
+                // メッセージボディがない。終了する
+                Log.v(TAG, " SEND BODY IS NOTHING.")
+                callbackReceiver.onReceivedFtpResponse(command.command, -1, "SEND COMMAND IS NOTHING.")
+                return
+            }
+            if (dataOutputStream == null)
+            {
+                Log.v(TAG, " DataOutputStream is null.")
+                callbackReceiver.onReceivedFtpResponse(command.command, -1, "DataOutputStream is null.")
+                return
+            }
+            if (isDumpReceiveLog)
+            {
+                // ログに送信メッセージを出力する
+                SimpleLogDumper.dump_bytes("SEND[" + byteArray.size + "] ", byteArray)
+            }
+
+            // (データを)送信
+            dataOutputStream?.write(byteArray)
+            dataOutputStream?.flush()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+            callbackReceiver.onReceivedFtpResponse(command.command, -1, e.message?:"EXCEPTION")
+        }
+    }
+
+    private fun waitForReceive(inputStream: InputStream, delayMs: Int, maxRetry: Int): Int
+    {
+        var retryCount = maxRetry
+        var isLogOutput = true
+        var readBytes = 0
+        try
+        {
+            while (readBytes <= 0)
+            {
+                sleep(delayMs)
+                readBytes = inputStream.available()
+                if (readBytes <= 0)
+                {
+                    if (isLogOutput)
+                    {
+                        // Log.v(TAG, "  ----- waitForReceive:: is.available() WAIT... : " + delayMs + "ms")
+                        isLogOutput = false
+                    }
+                    retryCount--
+                    if (retryCount < 0)
+                    {
+                        return (-1)
+                    }
+                }
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        return (readBytes)
+    }
+
+    private fun sleep(delayMs: Int)
+    {
+        try
+        {
+            Thread.sleep(delayMs.toLong())
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    companion object {
+        private val TAG = MyFtpClient::class.java.simpleName
+        private const val COMMAND_POLL_QUEUE_MS = 15
+        private const val DATA_POLL_QUEUE_MS = 50
+        private const val MAX_RETRY_WAIT_COUNT = 20
+        private const val MAX_RETRY_WAIT_COUNT_DATA = 100
+        private const val RECEIVE_WAIT_MS = 50
+        private const val PACKET_BUFFER_SIZE = 8192
+
+        private const val FTP_CONTROL_PORT = 21
+        // private const val FTP_DATA_PORT = 20
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/VisionKidsInterfaceProvider.kt b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/VisionKidsInterfaceProvider.kt
new file mode 100644 (file)
index 0000000..960fc95
--- /dev/null
@@ -0,0 +1,120 @@
+package net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper
+
+import androidx.appcompat.app.AppCompatActivity
+import net.osdn.gokigen.pkremote.IInformationReceiver
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraButtonControl
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraRunMode
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICaptureControl
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingControl
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingModeNotify
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IZoomLensControl
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IDisplayInjector
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewControl
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewListener
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.IPlaybackControl
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraHardwareStatus
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraInformation
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatus
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusWatcher
+import net.osdn.gokigen.pkremote.camera.vendor.visionkids.IVisionKidsInterfaceProvider
+import net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper.playback.VisionKidsPlaybackControl
+
+class VisionKidsInterfaceProvider(private val activity: AppCompatActivity, private val provider: ICameraStatusReceiver, private val informationReceiver: IInformationReceiver) : IVisionKidsInterfaceProvider, IDisplayInjector
+{
+    private val playbackControl = VisionKidsPlaybackControl()
+
+    // IDisplayInjector
+    override fun injectDisplay(frameDisplayer: IAutoFocusFrameDisplay?, indicator: IIndicatorControl?, focusingModeNotify: IFocusingModeNotify?)
+    {
+        // TODO("Not yet implemented")
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getVisionKidsCameraConnection(): ICameraConnection?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getLiveViewControl(): ILiveViewControl?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getLiveViewListener(): ILiveViewListener?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getFocusingControl(): IFocusingControl?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getCameraInformation(): ICameraInformation?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getZoomLensControl(): IZoomLensControl?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getCaptureControl(): ICaptureControl?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getDisplayInjector(): IDisplayInjector?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getCameraStatusListHolder(): ICameraStatus?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getButtonControl(): ICameraButtonControl?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getCameraStatusWatcher(): ICameraStatusWatcher?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getPlaybackControl(): IPlaybackControl
+    {
+        return (playbackControl)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getHardwareStatus(): ICameraHardwareStatus?
+    {
+        return (null)
+    }
+
+    // IVisionKidsInterfaceProvider
+    override fun getCameraRunMode(): ICameraRunMode?
+    {
+        return (null)
+    }
+
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsCameraConnectSequence.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsCameraConnectSequence.java
new file mode 100644 (file)
index 0000000..20e60fb
--- /dev/null
@@ -0,0 +1,188 @@
+package net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper.connection;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceManager;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver;
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+import net.osdn.gokigen.pkremote.preference.IPreferencePropertyAccessor;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+/**
+ *   VisionKidsとの接続シーケンス
+ *
+ */
+public class VisionKidsCameraConnectSequence implements Runnable
+{
+    private final String TAG = this.toString();
+    private final Activity context;
+    private final ICameraConnection cameraConnection;
+    private final ICameraStatusReceiver cameraStatusReceiver;
+
+    VisionKidsCameraConnectSequence(@NonNull Activity context, @NonNull ICameraStatusReceiver statusReceiver, @NonNull final ICameraConnection cameraConnection)
+    {
+        Log.v(TAG, "VisionKidsCameraConnectSequence");
+        this.context = context;
+        this.cameraConnection = cameraConnection;
+        this.cameraStatusReceiver = statusReceiver;
+    }
+
+    @Override
+    public void run()
+    {
+        try
+        {
+            final String oscInfoUrl = "http://192.168.1.1/osc/info";
+            final int TIMEOUT_MS = 5000;
+
+            SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+            boolean useThetaV21 = preferences.getBoolean(IPreferencePropertyAccessor.USE_OSC_THETA_V21, false);
+
+            String response = SimpleHttpClient.httpGet(oscInfoUrl, TIMEOUT_MS);
+            Log.v(TAG, " " + oscInfoUrl + " " + response);
+            if (response.length() > 0)
+            {
+                try
+                {
+                    JSONArray apiLevelArray = new JSONObject(response).getJSONArray("apiLevel");
+                    int size = apiLevelArray.length();
+                    for (int index = 0; index < size; index++)
+                    {
+                        int api = apiLevelArray.getInt(index);
+                        if ((api == 2)&&(useThetaV21))
+                        {
+                            // API Level V2.1を使用して通信する
+                            connectApiV21();
+                            return;
+                        }
+                    }
+                }
+                catch (Exception e)
+                {
+                    e.printStackTrace();
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            // onConnectError(e.getLocalizedMessage());
+        }
+
+        // API Level V2 を使用して通信する
+        connectApiV2();
+    }
+
+    /**
+     *
+     */
+    private void connectApiV2()
+    {
+        final String commandsExecuteUrl = "http://192.168.1.1/osc/commands/execute";
+        final String startSessionData = "{\"name\":\"camera.startSession\",\"parameters\":{\"timeout\":0}}";
+        final String getStateUrl = "http://192.168.1.1/osc/state";
+        final int TIMEOUT_MS = 2000;
+
+        try
+        {
+            String response = SimpleHttpClient.httpPostWithHeader(commandsExecuteUrl, startSessionData, null, "application/json;charset=utf-8", TIMEOUT_MS);
+            Log.v(TAG, " " + commandsExecuteUrl + " " + startSessionData + " " + response);
+
+            String response2 = SimpleHttpClient.httpPostWithHeader(getStateUrl, "", null, "application/json;charset=utf-8", TIMEOUT_MS);
+            Log.v(TAG, " " + getStateUrl + " " + response2);
+
+            onConnectNotify();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            onConnectError(e.getLocalizedMessage());
+        }
+    }
+
+    private void connectApiV21()
+    {
+        final String commandsExecuteUrl = "http://192.168.1.1/osc/commands/execute";
+        final String startSessionData = "{\"name\":\"camera.startSession\",\"parameters\":{\"timeout\":0}}";
+        final String getStateUrl = "http://192.168.1.1/osc/state";
+        final int TIMEOUT_MS = 5000;
+
+        try
+        {
+            String responseS = SimpleHttpClient.httpPostWithHeader(commandsExecuteUrl, startSessionData, null, "application/json;charset=utf-8", TIMEOUT_MS);
+            Log.v(TAG, " " + commandsExecuteUrl + " " + startSessionData + " " + responseS);
+
+            String response = SimpleHttpClient.httpPostWithHeader(getStateUrl, "", null, "application/json;charset=utf-8", TIMEOUT_MS);
+            Log.v(TAG, " " + getStateUrl + " " + response);
+            if (response.length() > 0)
+            {
+                int apiLevel = 1;
+               JSONObject object = new JSONObject(response);
+               try
+               {
+                   apiLevel = object.getJSONObject("state").getInt("_apiVersion");
+               }
+               catch (Exception e)
+               {
+                   e.printStackTrace();
+               }
+               if (apiLevel != 2)
+               {
+                   JSONObject jsonObject = object.getJSONObject("state");
+                   String sessionId = jsonObject.getString("sessionId");
+                   String setApiLevelData = "{\"name\":\"camera.setOptions\",\"parameters\":{" + "\"sessionId\" : \"" + sessionId + "\", \"options\":{ \"clientVersion\":2}}}";
+
+                   String response3 = SimpleHttpClient.httpPostWithHeader(commandsExecuteUrl, setApiLevelData, null, "application/json;charset=utf-8", TIMEOUT_MS);
+                   Log.v(TAG, " " + commandsExecuteUrl + " " + setApiLevelData + " " + response3);
+               }
+                onConnectNotify();
+            }
+            else
+            {
+                onConnectError(context.getString(R.string.camera_not_found));
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            onConnectError(e.getLocalizedMessage());
+        }
+    }
+
+    private void onConnectNotify()
+    {
+        try
+        {
+            final Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    // カメラとの接続確立を通知する
+                    cameraStatusReceiver.onStatusNotify(context.getString(R.string.connect_connected));
+                    cameraStatusReceiver.onCameraConnected();
+                    Log.v(TAG, "onConnectNotify()");
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    private void onConnectError(String reason)
+    {
+        cameraConnection.alertConnectingFailed(reason);
+    }
+
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsCameraDisconnectSequence.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsCameraDisconnectSequence.java
new file mode 100644 (file)
index 0000000..eb289a6
--- /dev/null
@@ -0,0 +1,20 @@
+package net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper.connection;
+
+import android.util.Log;
+
+public class VisionKidsCameraDisconnectSequence implements Runnable
+{
+    private final String TAG = this.toString();
+
+    VisionKidsCameraDisconnectSequence()
+    {
+        // なにもしない
+    }
+
+    @Override
+    public void run()
+    {
+        // なにもしない(というかできない)
+        Log.v(TAG, " Power off (VisionKids)");
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsCameraPowerOff.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsCameraPowerOff.java
new file mode 100644 (file)
index 0000000..7d630c6
--- /dev/null
@@ -0,0 +1,80 @@
+package net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper.connection;
+
+import android.content.Context;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.preference.IPreferencePropertyAccessor;
+import net.osdn.gokigen.pkremote.scene.ConfirmationDialog;
+import net.osdn.gokigen.pkremote.scene.IChangeScene;
+
+import androidx.preference.Preference;
+
+/**
+ *  Preferenceがクリックされた時に処理するクラス
+ *
+ */
+public class VisionKidsCameraPowerOff implements Preference.OnPreferenceClickListener, ConfirmationDialog.Callback
+{
+    private final Context context;
+    private final IChangeScene changeScene;
+    private String preferenceKey = null;
+
+    /**
+     *   コンストラクタ
+     *
+     */
+    public VisionKidsCameraPowerOff(Context context, IChangeScene changeScene)
+    {
+        this.context = context;
+        this.changeScene = changeScene;
+    }
+
+    /**
+     *   クラスの準備
+     *
+     */
+    public void prepare()
+    {
+        // 何もしない
+    }
+
+    /**
+     *
+     *
+     * @param preference クリックしたpreference
+     * @return false : ハンドルしない / true : ハンドルした
+     */
+    @Override
+    public boolean onPreferenceClick(Preference preference)
+    {
+        if (!preference.hasKey())
+        {
+            return (false);
+        }
+
+        preferenceKey = preference.getKey();
+        if (preferenceKey.contains(IPreferencePropertyAccessor.EXIT_APPLICATION))
+        {
+
+            // 確認ダイアログの生成と表示
+            ConfirmationDialog dialog = ConfirmationDialog.newInstance(context);
+            dialog.show(R.string.dialog_title_confirmation, R.string.dialog_message_exit_application, this);
+            return (true);
+        }
+        return (false);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void confirm()
+    {
+        if (preferenceKey.contains(IPreferencePropertyAccessor.EXIT_APPLICATION))
+        {
+            // アプリケーションを終了する。
+            changeScene.exitApplication();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsConnection.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/connection/VisionKidsConnection.java
new file mode 100644 (file)
index 0000000..d4e9064
--- /dev/null
@@ -0,0 +1,252 @@
+package net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper.connection;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class VisionKidsConnection implements ICameraConnection
+{
+    private final String TAG = toString();
+    private final Activity context;
+    private final ICameraStatusReceiver statusReceiver;
+    private final BroadcastReceiver connectionReceiver;
+    private final Executor cameraExecutor = Executors.newFixedThreadPool(1);
+
+    private CameraConnectionStatus connectionStatus = CameraConnectionStatus.UNKNOWN;
+
+
+    /**
+     *
+     *
+     */
+    public VisionKidsConnection(@NonNull final Activity context, @NonNull final ICameraStatusReceiver statusReceiver)
+    {
+        Log.v(TAG, "ThetaConnection()");
+        this.context = context;
+        this.statusReceiver = statusReceiver;
+        connectionReceiver = new BroadcastReceiver()
+        {
+            @Override
+            public void onReceive(Context context, Intent intent)
+            {
+                onReceiveBroadcastOfConnection(context, intent);
+            }
+        };
+    }
+
+    /**
+     *
+     *
+     */
+    private void onReceiveBroadcastOfConnection(Context context, Intent intent)
+    {
+        statusReceiver.onStatusNotify(context.getString(R.string.connect_check_wifi));
+        Log.v(TAG,context.getString(R.string.connect_check_wifi));
+
+        String action = intent.getAction();
+        if (action == null)
+        {
+            //
+            Log.v(TAG, "intent.getAction() : null");
+            return;
+        }
+
+        try
+        {
+            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION))
+            {
+                Log.v(TAG, "onReceiveBroadcastOfConnection() : CONNECTIVITY_ACTION");
+
+                WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+                if (wifiManager != null) {
+                    WifiInfo info = wifiManager.getConnectionInfo();
+                    if (wifiManager.isWifiEnabled() && info != null)
+                    {
+                        if (info.getNetworkId() != -1)
+                        {
+                            Log.v(TAG, "Network ID is -1, there is no currently connected network.");
+                        }
+                        // 自動接続が指示されていた場合は、カメラとの接続処理を行う
+                        connectToCamera();
+                    } else {
+                        if (info == null)
+                        {
+                            Log.v(TAG, "NETWORK INFO IS NULL.");
+                        } else {
+                            Log.v(TAG, "isWifiEnabled : " + wifiManager.isWifiEnabled() + " NetworkId : " + info.getNetworkId());
+                        }
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            Log.w(TAG, "onReceiveBroadcastOfConnection() EXCEPTION" + e.getMessage());
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void startWatchWifiStatus(Context context)
+    {
+        Log.v(TAG, "startWatchWifiStatus()");
+        statusReceiver.onStatusNotify("prepare");
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        context.registerReceiver(connectionReceiver, filter);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void stopWatchWifiStatus(Context context)
+    {
+        Log.v(TAG, "stopWatchWifiStatus()");
+        context.unregisterReceiver(connectionReceiver);
+        disconnect(false);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void disconnect(boolean powerOff)
+    {
+        Log.v(TAG, "disconnect()");
+        disconnectFromCamera(powerOff);
+        connectionStatus = CameraConnectionStatus.DISCONNECTED;
+        statusReceiver.onCameraDisconnected();
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void connect()
+    {
+        Log.v(TAG, "connect()");
+        connectToCamera();
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void alertConnectingFailed(String message)
+    {
+        Log.v(TAG, "alertConnectingFailed() : " + message);
+        final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+                .setTitle(context.getString(R.string.dialog_title_connect_failed_theta))
+                .setMessage(message)
+                .setPositiveButton(context.getString(R.string.dialog_title_button_retry), (dialog, which) -> connect())
+                .setNeutralButton(R.string.dialog_title_button_network_settings, (dialog, which) -> {
+                    try
+                    {
+                        // Wifi 設定画面を表示する
+                        context.startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
+                    }
+                    catch (android.content.ActivityNotFoundException ex)
+                    {
+                        // Activity が存在しなかった...設定画面が起動できなかった
+                        Log.v(TAG, "android.content.ActivityNotFoundException...");
+
+                        // この場合は、再試行と等価な動きとする
+                        connect();
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                });
+        context.runOnUiThread(() -> {
+            try
+            {
+                builder.show();
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        });
+    }
+
+    @Override
+    public CameraConnectionStatus getConnectionStatus()
+    {
+        Log.v(TAG, "getConnectionStatus()");
+        return (connectionStatus);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void forceUpdateConnectionStatus(CameraConnectionStatus status)
+    {
+        Log.v(TAG, "forceUpdateConnectionStatus()");
+        connectionStatus = status;
+    }
+
+    /**
+     * カメラとの切断処理
+     */
+    private void disconnectFromCamera(final boolean powerOff)
+    {
+        Log.v(TAG, "disconnectFromCamera()");
+        try
+        {
+            cameraExecutor.execute(new VisionKidsCameraDisconnectSequence());
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * カメラとの接続処理
+     */
+    private void connectToCamera()
+    {
+        Log.v(TAG, "connectToCamera()");
+        connectionStatus = CameraConnectionStatus.CONNECTING;
+        try
+        {
+            cameraExecutor.execute(new VisionKidsCameraConnectSequence(context, statusReceiver, this));
+        }
+        catch (Exception e)
+        {
+            Log.v(TAG, "connectToCamera() EXCEPTION : " + e.getMessage());
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/playback/VisionKidsPlaybackControl.kt b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/visionkids/wrapper/playback/VisionKidsPlaybackControl.kt
new file mode 100644 (file)
index 0000000..f3be20d
--- /dev/null
@@ -0,0 +1,64 @@
+package net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper.playback
+
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.ICameraContentListCallback
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.ICameraFileInfo
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.IContentInfoCallback
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.IDownloadContentCallback
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.IDownloadContentListCallback
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.IDownloadThumbnailImageCallback
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.IPlaybackControl
+
+class VisionKidsPlaybackControl: IPlaybackControl
+{
+    override fun getRawFileSuffix(): String
+    {
+        return (".DNG")
+    }
+
+    override fun downloadContentList(callback: IDownloadContentListCallback?) {
+        //TODO("Not yet implemented")
+    }
+
+    override fun getContentInfo(path: String?, name: String?, callback: IContentInfoCallback?) {
+        //TODO("Not yet implemented")
+    }
+
+    override fun updateCameraFileInfo(info: ICameraFileInfo?) {
+        //TODO("Not yet implemented")
+    }
+
+    override fun downloadContentScreennail(
+        path: String?,
+        callback: IDownloadThumbnailImageCallback?
+    ) {
+        //TODO("Not yet implemented")
+    }
+
+    override fun downloadContentThumbnail(
+        path: String?,
+        callback: IDownloadThumbnailImageCallback?
+    ) {
+        //TODO("Not yet implemented")
+    }
+
+    override fun downloadContent(
+        path: String?,
+        isSmallSize: Boolean,
+        callback: IDownloadContentCallback?
+    ) {
+        //TODO("Not yet implemented")
+    }
+
+    override fun getCameraContentList(callback: ICameraContentListCallback?) {
+        //TODO("Not yet implemented")
+    }
+
+    override fun showPictureStarted() {
+        //TODO("Not yet implemented")
+    }
+
+    override fun showPictureFinished() {
+        //TODO("Not yet implemented")
+    }
+
+}
\ No newline at end of file
index 751371e..df0b786 100644 (file)
@@ -140,6 +140,20 @@ public interface IPreferencePropertyAccessor
     String CANON_SMALL_PICTURE_TYPE = "canon_small_picture_type";
     String CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE = "0";
 
+    String VISIONKIDS_HOST_IP = "visionkids_host_ip";
+    String VISIONKIDS_HOST_IP_DEFAULT_VALUE = "192.168.4.100";
+
+    String VISIONKIDS_FTP_USER = "visionkids_ftp_user";
+    String VISIONKIDS_FTP_USER_DEFAULT_VALUE = "ftp";
+
+    String VISIONKIDS_FTP_PASS = "visionkids_ftp_pass";
+    String VISIONKIDS_FTP_PASS_DEFAULT_VALUE = "ftp";
+
+    String VISIONKIDS_LIST_TIMEOUT = "visionkids_get_pics_list_timeout";
+    String VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE = "30";
+
+
+
 /*
     //String GR2_DISPLAY_MODE = "gr2_display_mode";
     //String GR2_DISPLAY_MODE_DEFAULT_VALUE = "0";
index 8a3e480..2ff9c84 100644 (file)
@@ -182,6 +182,18 @@ public class CanonPreferenceFragment  extends PreferenceFragmentCompat implement
             if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
                 editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE);
             }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP, IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER, IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS, IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT, IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE);
+            }
             editor.apply();
         }
         catch (Exception e)
index 9c5c320..fefb09a 100644 (file)
@@ -182,6 +182,18 @@ public class FujiXPreferenceFragment  extends PreferenceFragmentCompat implement
             if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
                 editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE);
             }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP, IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER, IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS, IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT, IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE);
+            }
             editor.apply();
         }
         catch (Exception e)
index 19233a3..486329a 100644 (file)
@@ -179,6 +179,18 @@ public class NikonPreferenceFragment  extends PreferenceFragmentCompat implement
             if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
                 editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE);
             }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP, IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER, IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS, IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT, IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE);
+            }
             editor.apply();
         }
         catch (Exception e)
index 4d86cf4..bf37731 100644 (file)
@@ -208,6 +208,18 @@ public class OpcPreferenceFragment extends PreferenceFragmentCompat implements S
         if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
             editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE);
         }
+        if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+            editor.putString(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP, IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE);
+        }
+        if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+            editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER, IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE);
+        }
+        if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+            editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS, IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE);
+        }
+        if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+            editor.putString(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT, IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE);
+        }
         editor.apply();
     }
 
index 74ffa82..da27f45 100644 (file)
@@ -173,6 +173,18 @@ public class OlympusPenPreferenceFragment  extends PreferenceFragmentCompat impl
             if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
                 editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE);
             }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP, IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER, IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS, IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT, IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE);
+            }
             editor.apply();
         }
         catch (Exception e)
index aac176c..77426f0 100644 (file)
@@ -171,6 +171,18 @@ public class PanasonicPreferenceFragment  extends PreferenceFragmentCompat imple
             if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
                 editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE);
             }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP, IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER, IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS, IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT, IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE);
+            }
             editor.apply();
         }
         catch (Exception e)
index f6e872c..f439089 100644 (file)
@@ -170,6 +170,18 @@ public class PixproPreferenceFragment  extends PreferenceFragmentCompat implemen
             if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
                 editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE);
             }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP, IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER, IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS, IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT, IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE);
+            }
             editor.apply();
         }
         catch (Exception e)
index 623a6af..a85e00a 100644 (file)
@@ -186,6 +186,18 @@ public class RicohGr2PreferenceFragment  extends PreferenceFragmentCompat implem
             if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
                 editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE);
             }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP, IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER, IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS, IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT, IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE);
+            }
             editor.apply();
         }
         catch (Exception e)
index 278dc5e..54b7311 100644 (file)
@@ -168,6 +168,18 @@ public class SonyPreferenceFragment  extends PreferenceFragmentCompat implements
             if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
                 editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE);
             }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP, IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER, IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS, IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT, IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE);
+            }
             editor.apply();
         }
         catch (Exception e)
index 4c2b943..ce0ae17 100644 (file)
@@ -170,6 +170,18 @@ public class ThetaPreferenceFragment  extends PreferenceFragmentCompat implement
             if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
                 editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE);
             }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP, IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER, IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS, IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+                editor.putString(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT, IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE);
+            }
             editor.apply();
         }
         catch (Exception e)
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/visionkids/VisionKidsPreferenceFragment.kt b/app/src/main/java/net/osdn/gokigen/pkremote/preference/visionkids/VisionKidsPreferenceFragment.kt
new file mode 100644 (file)
index 0000000..1efa88c
--- /dev/null
@@ -0,0 +1,413 @@
+package net.osdn.gokigen.pkremote.preference.visionkids
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.provider.Settings
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.preference.CheckBoxPreference
+import androidx.preference.ListPreference
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceManager
+import net.osdn.gokigen.pkremote.R
+import net.osdn.gokigen.pkremote.camera.vendor.visionkids.wrapper.connection.VisionKidsCameraPowerOff
+import net.osdn.gokigen.pkremote.logcat.LogCatViewer
+import net.osdn.gokigen.pkremote.preference.IPreferencePropertyAccessor
+import net.osdn.gokigen.pkremote.scene.IChangeScene
+
+class VisionKidsPreferenceFragment: PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, Preference.OnPreferenceClickListener
+{
+    private var context: AppCompatActivity? = null
+    private var preferences: SharedPreferences? = null
+    private var logCatViewer: LogCatViewer? = null
+    private var powerOffController : VisionKidsCameraPowerOff? = null
+
+    /**
+     *
+     *
+     */
+    private fun prepare(context: AppCompatActivity, changeScene: IChangeScene)
+    {
+        try
+        {
+            logCatViewer = LogCatViewer(changeScene)
+            logCatViewer?.prepare()
+            this.powerOffController = VisionKidsCameraPowerOff(context, changeScene)
+            this.powerOffController?.prepare()
+            this.context = context
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    override fun onAttach(activity: Context)
+    {
+        super.onAttach(activity)
+        Log.v(TAG, "onAttach()")
+        try
+        {
+            // Preference をつかまえる
+            preferences = PreferenceManager.getDefaultSharedPreferences(activity)
+
+            // Preference を初期設定する
+            initializePreferences()
+            preferences?.registerOnSharedPreferenceChangeListener(this)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    /**
+     * Preferenceの初期化...
+     *
+     */
+    private fun initializePreferences() {
+        try {
+            val items = preferences!!.all
+            val editor = preferences!!.edit()
+            if (!items.containsKey(IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA)) {
+                editor.putBoolean(IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA, true)
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW)) {
+                editor.putBoolean(
+                    IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW,
+                    true
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.CONNECTION_METHOD)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.CONNECTION_METHOD,
+                    IPreferencePropertyAccessor.CONNECTION_METHOD_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.GET_SMALL_PICTURE_AS_VGA)) {
+                editor.putBoolean(IPreferencePropertyAccessor.GET_SMALL_PICTURE_AS_VGA, false)
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.USE_SMARTPHONE_TRANSFER_MODE)) {
+                editor.putBoolean(IPreferencePropertyAccessor.USE_SMARTPHONE_TRANSFER_MODE, false)
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.RICOH_GET_PICS_LIST_TIMEOUT)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.RICOH_GET_PICS_LIST_TIMEOUT,
+                    IPreferencePropertyAccessor.RICOH_GET_PICS_LIST_TIMEOUT_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.USE_OSC_THETA_V21)) {
+                editor.putBoolean(IPreferencePropertyAccessor.USE_OSC_THETA_V21, false)
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.PIXPRO_HOST_IP)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.PIXPRO_HOST_IP,
+                    IPreferencePropertyAccessor.PIXPRO_HOST_IP_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.PIXPRO_COMMAND_PORT)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.PIXPRO_COMMAND_PORT,
+                    IPreferencePropertyAccessor.PIXPRO_COMMAND_PORT_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.PIXPRO_GET_PICS_LIST_TIMEOUT)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.PIXPRO_GET_PICS_LIST_TIMEOUT,
+                    IPreferencePropertyAccessor.PIXPRO_GET_PICS_LIST_TIMEOUT_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.THUMBNAIL_IMAGE_CACHE_SIZE)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.THUMBNAIL_IMAGE_CACHE_SIZE,
+                    IPreferencePropertyAccessor.THUMBNAIL_IMAGE_CACHE_SIZE_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.CANON_HOST_IP)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.CANON_HOST_IP,
+                    IPreferencePropertyAccessor.CANON_HOST_IP_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE,
+                    IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE,
+                    IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_HOST_IP)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.VISIONKIDS_HOST_IP,
+                    IPreferencePropertyAccessor.VISIONKIDS_HOST_IP_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_USER)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.VISIONKIDS_FTP_USER,
+                    IPreferencePropertyAccessor.VISIONKIDS_FTP_USER_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS,
+                    IPreferencePropertyAccessor.VISIONKIDS_FTP_PASS_DEFAULT_VALUE
+                )
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT)) {
+                editor.putString(
+                    IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT,
+                    IPreferencePropertyAccessor.VISIONKIDS_LIST_TIMEOUT_DEFAULT_VALUE
+                )
+            }
+            editor.apply()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?)
+    {
+        Log.v(TAG, "onSharedPreferenceChanged() : $key")
+        val value: Boolean
+        if (key != null)
+        {
+            when (key)
+            {
+                IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA -> {
+                    value = preferences?.getBoolean(key, true)?: true
+                    Log.v(TAG, " $key , $value")
+                }
+
+                IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW -> {
+                    value = preferences?.getBoolean(key, true)?: true
+                    Log.v(TAG, " $key , $value")
+                }
+
+                IPreferencePropertyAccessor.USE_OSC_THETA_V21 -> {
+                    value = preferences?.getBoolean(key, false)?: false
+                    Log.v(TAG, " $key , $value")
+                }
+
+                else -> {
+                    val strValue = preferences?.getString(key, "")?: ""
+                    setListPreference(key, key, strValue)
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?)
+    {
+        Log.v(TAG, "onCreatePreferences()")
+        try
+        {
+            //super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.preferences_visionkids)
+            val connectionMethod =
+                findPreference<ListPreference>(IPreferencePropertyAccessor.CONNECTION_METHOD)
+            if (connectionMethod != null)
+            {
+                connectionMethod.onPreferenceChangeListener =
+                    Preference.OnPreferenceChangeListener { preference, newValue ->
+                        preference.summary = "$newValue "
+                        true
+                    }
+                connectionMethod.summary = connectionMethod.value + " "
+            }
+            setOnPreferenceClickListener(
+                IPreferencePropertyAccessor.EXIT_APPLICATION,
+                powerOffController
+            )
+            setOnPreferenceClickListener(IPreferencePropertyAccessor.DEBUG_INFO, logCatViewer)
+            setOnPreferenceClickListener(IPreferencePropertyAccessor.WIFI_SETTINGS, this)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    private fun setOnPreferenceClickListener(key: String, listener: Preference.OnPreferenceClickListener?)
+    {
+        val preference = findPreference<Preference>(key)
+        if (preference != null)
+        {
+            preference.onPreferenceClickListener = listener
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    override fun onResume() {
+        super.onResume()
+        Log.v(TAG, "onResume() Start")
+        try {
+            synchronizedProperty()
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        Log.v(TAG, "onResume() End")
+    }
+
+    /**
+     *
+     *
+     */
+    override fun onPause()
+    {
+        super.onPause()
+        Log.v(TAG, "onPause() Start")
+        try
+        {
+            // Preference変更のリスナを解除
+            preferences!!.unregisterOnSharedPreferenceChangeListener(this)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        Log.v(TAG, "onPause() End")
+    }
+
+    /**
+     * ListPreference の表示データを設定
+     *
+     * @param pref_key     Preference(表示)のキー
+     * @param key          Preference(データ)のキー
+     * @param defaultValue Preferenceのデフォルト値
+     */
+    private fun setListPreference(pref_key: String, key: String, defaultValue: String?)
+    {
+        try
+        {
+            val pref: ListPreference? = findPreference(pref_key)
+            val value = preferences?.getString(key, defaultValue)
+            if (pref != null)
+            {
+                pref.value = value
+                pref.summary = value
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    /**
+     * BooleanPreference の表示データを設定
+     *
+     * @param pref_key     Preference(表示)のキー
+     * @param key          Preference(データ)のキー
+     * @param defaultValue Preferenceのデフォルト値
+     */
+    private fun setBooleanPreference(pref_key: String, key: String, defaultValue: Boolean)
+    {
+        try
+        {
+            val pref = findPreference<CheckBoxPreference>(pref_key)
+            if (pref != null)
+            {
+                val value = preferences?.getBoolean(key, defaultValue)?: defaultValue
+                pref.isChecked = value
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    private fun synchronizedProperty()
+    {
+        val activity = activity
+        val defaultValue = true
+        activity?.runOnUiThread {
+            try
+            {
+                // Preferenceの画面に反映させる
+                setBooleanPreference(
+                    IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA,
+                    IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA,
+                    defaultValue
+                )
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+            }
+        }
+    }
+
+    override fun onPreferenceClick(preference: Preference): Boolean
+    {
+        try
+        {
+            val preferenceKey = preference.key
+            Log.v(TAG, " onPreferenceClick : $preferenceKey")
+            if (preferenceKey.contains(IPreferencePropertyAccessor.WIFI_SETTINGS))
+            {
+                // Wifi 設定画面を表示する
+                if (context != null)
+                {
+                    context?.startActivity(Intent(Settings.ACTION_WIFI_SETTINGS))
+                }
+            }
+            return (true)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        return (false)
+    }
+
+    companion object
+    {
+        private val TAG = VisionKidsPreferenceFragment::class.java.simpleName
+
+        /**
+         *
+         *
+         */
+        @JvmStatic fun newInstance(context: AppCompatActivity, changeScene: IChangeScene): VisionKidsPreferenceFragment
+        {
+            val instance = VisionKidsPreferenceFragment()
+            instance.prepare(context, changeScene)
+
+            // パラメータはBundleにまとめておく
+            val arguments = Bundle()
+            instance.arguments = arguments
+            return (instance)
+        }
+    }
+}
index 4ac0d96..2fe8927 100644 (file)
@@ -25,6 +25,7 @@ import net.osdn.gokigen.pkremote.preference.pixpro.PixproPreferenceFragment;
 import net.osdn.gokigen.pkremote.preference.ricohgr2.RicohGr2PreferenceFragment;
 import net.osdn.gokigen.pkremote.preference.sony.SonyPreferenceFragment;
 import net.osdn.gokigen.pkremote.preference.theta.ThetaPreferenceFragment;
+import net.osdn.gokigen.pkremote.preference.visionkids.VisionKidsPreferenceFragment;
 import net.osdn.gokigen.pkremote.transfer.AutoTransferFragment;
 
 import androidx.annotation.NonNull;
@@ -345,6 +346,10 @@ public class CameraSceneUpdater implements ICameraStatusReceiver, IChangeScene,
                     {
                         preferenceFragment = PixproPreferenceFragment.newInstance(activity, this);
                     }
+                    else if (connectionMethod == ICameraConnection.CameraConnectionMethod.VISIONKIDS)
+                    {
+                        preferenceFragment = VisionKidsPreferenceFragment.newInstance(activity, this);
+                    }
                     else //  if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC)
                     {
                         preferenceFragment = OpcPreferenceFragment.newInstance(activity, interfaceProvider, this);
index 5c069ef..6bad100 100644 (file)
@@ -11,6 +11,7 @@
         <item>Sony</item>
         <item>Canon</item>
         <item>Nikon</item>
+        <item>Vision Kids</item>
     </string-array>
 
     <string-array name="connection_method_value">
@@ -24,6 +25,7 @@
         <item>SONY</item>
         <item>CANON</item>
         <item>NIKON</item>
+        <item>VISIONKIDS</item>
     </string-array>
 
     <string-array name="sound_volume_level">
index 854c30d..2c09c3d 100644 (file)
     <string name="pref_canon_small_picture_type">スモール画像取得シーケンス</string>
     <string name="pref_summary_canon_small_picture_type">スモール画像がうまく取得できない場合、設定を変更してみてください。 (初期値: TYPE0)</string>
 
+    <string name="pref_visionkids_host_ip">カメラIPアドレス</string>
+    <string name="pref_summary_visionkids_host_ip">通常、変更は不要です (初期値:192.168.4.100)</string>
+
+    <string name="pref_visionkids_ftp_user">カメラアクセス用設定(User)</string>
+    <string name="pref_summary_visionkids_ftp_user">通常、変更は不要です (初期値:ftp)</string>
+
+    <string name="pref_visionkids_ftp_pass">カメラアクセス用設定(Pass)</string>
+    <string name="pref_summary_visionkids_ftp_pass">通常、変更は不要です (初期値:ftp)</string>
+
+    <string name="pref_visionkids_get_pics_list_timeout">撮影画像一覧取得時のタイムアウト(単位:秒)</string>
+    <string name="pref_summary_visionkids_get_pics_list_timeout">通常、変更は不要です (初期値:30)</string>
+
 </resources>
index d89aea3..ba1bb3e 100644 (file)
@@ -11,6 +11,7 @@
         <item>Sony</item>
         <item>Canon</item>
         <item>Nikon</item>
+        <item>Vision Kids</item>
     </string-array>
 
     <string-array name="connection_method_value">
@@ -24,6 +25,7 @@
         <item>SONY</item>
         <item>CANON</item>
         <item>NIKON</item>
+        <item>VISIONKIDS</item>
     </string-array>
 
     <string-array name="sound_volume_level">
index e6f6218..0d751a8 100644 (file)
     <string name="pref_canon_small_picture_type">Small Picture Getting Sequence</string>
     <string name="pref_summary_canon_small_picture_type">default: TYPE0 </string>
 
+    <string name="pref_visionkids_host_ip">Camera IP Address</string>
+    <string name="pref_summary_visionkids_host_ip">default: 192.168.4.100 </string>
+
+    <string name="pref_visionkids_ftp_user">Camera Access User</string>
+    <string name="pref_summary_visionkids_ftp_user">default: ftp </string>
+
+    <string name="pref_visionkids_ftp_pass">Camera Access Pass</string>
+    <string name="pref_summary_visionkids_ftp_pass">default: ftp </string>
+
+    <string name="pref_visionkids_get_pics_list_timeout">Get Pics List Timeout(unit: sec.)</string>
+    <string name="pref_summary_visionkids_get_pics_list_timeout">If the camera has many number of Pics, increase number.</string>
+
 </resources>
diff --git a/app/src/main/res/xml/preferences_visionkids.xml b/app/src/main/res/xml/preferences_visionkids.xml
new file mode 100644 (file)
index 0000000..8c1a7ca
--- /dev/null
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
+    <PreferenceCategory
+        android:title="@string/pref_cat_application_control">
+
+        <PreferenceScreen
+            android:key="exit_application"
+            android:icon="@drawable/ic_power_settings_new_black_24dp"
+            android:title="@string/pref_exit_power_off_pixpro" />
+
+        <ListPreference
+            android:title="@string/pref_connection_method"
+            android:entryValues="@array/connection_method_value"
+            android:entries="@array/connection_method"
+            android:key="connection_method"
+            android:defaultValue="OPC"/>
+
+        <PreferenceScreen
+            android:key="wifi_settings"
+            android:title="@string/pref_wifi_settings"
+            android:summary="@string/pref_summary_wifi_settings" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/pref_cat_camera">
+
+        <EditTextPreference
+            android:key="visionkids_host_ip"
+            android:title="@string/pref_visionkids_host_ip"
+            android:defaultValue="192.168.4.100"
+            android:summary="@string/pref_summary_visionkids_host_ip" />
+
+        <EditTextPreference
+            android:key="visionkids_ftp_user"
+            android:title="@string/pref_visionkids_ftp_user"
+            android:defaultValue="ftp"
+            android:summary="@string/pref_summary_visionkids_ftp_user" />
+
+        <EditTextPreference
+            android:key="visionkids_ftp_pass"
+            android:title="@string/pref_visionkids_ftp_pass"
+            android:defaultValue="ftp"
+            android:inputType="textVisiblePassword"
+            android:summary="@string/pref_summary_visionkids_ftp_pass" />
+
+        <EditTextPreference
+            android:key="visionkids_get_pics_list_timeout"
+            android:title="@string/pref_visionkids_get_pics_list_timeout"
+            android:defaultValue="30"
+            android:inputType="number"
+            android:summary="@string/pref_summary_visionkids_get_pics_list_timeout" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/pref_cat_initialize">
+
+        <CheckBoxPreference
+            android:key="auto_connect_to_camera"
+            android:title="@string/pref_auto_connect_camera"
+            android:summary="@string/pref_summary_auto_connect_camera" />
+
+        <EditTextPreference
+            android:key="thumbnail_image_cache_size"
+            android:title="@string/pref_thumbnail_image_cache_size"
+            android:defaultValue="120"
+            android:inputType="number"
+            android:summary="@string/pref_summary_thumbnail_image_cache_size" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/pref_cat_gokigen">
+
+        <Preference
+            android:key="instruction_link"
+            android:title="@string/pref_instruction_manual"
+            android:summary="https://osdn.net/projects/gokigen/wiki/PKRemote"
+            android:selectable="true">
+            <intent android:action="android.intent.action.VIEW"
+                android:data="https://osdn.net/projects/gokigen/wiki/PKRemote" />
+        </Preference>
+
+        <Preference
+            android:key="privacy_policy"
+            android:title="@string/pref_privacy_policy"
+            android:summary="https://osdn.net/projects/gokigen/wiki/PrivacyPolicy"
+            android:selectable="true">
+            <intent android:action="android.intent.action.VIEW"
+                android:data="https://osdn.net/projects/gokigen/wiki/PrivacyPolicy" />
+        </Preference>
+
+        <PreferenceScreen
+            android:key="debug_info"
+            android:title="@string/pref_degug_info"
+            android:summary="@string/pref_summary_debug_info" />
+
+    </PreferenceCategory>
+</PreferenceScreen>