OSDN Git Service

namespaceを少し整理。
[gokigen/mangle.git] / app / src / main / java / jp / osdn / gokigen / gokigenassets / camera / vendor / sony / wrapper / connection / SonySsdpClient.kt
1 package jp.osdn.gokigen.gokigenassets.camera.vendor.sony.wrapper.connection
2
3 import android.content.Context
4 import android.util.Log
5 import jp.osdn.gokigen.gokigenassets.camera.interfaces.ICameraStatusReceiver
6 import jp.osdn.gokigen.gokigenassets.camera.vendor.sony.wrapper.ISonyCamera
7 import jp.osdn.gokigen.gokigenassets.camera.vendor.sony.wrapper.SonyCameraDeviceProvider
8 import jp.osdn.gokigen.gokigenassets.constants.ICameraConstantConvert
9 import jp.osdn.gokigen.gokigenassets.utils.communication.SimpleHttpClient
10 import jp.osdn.gokigen.gokigenassets.utils.communication.XmlElement
11 import java.net.DatagramPacket
12 import java.net.DatagramSocket
13 import java.net.InetSocketAddress
14 import java.nio.charset.Charset
15
16 class SonySsdpClient(private val context: Context, private val callback: ISearchResultCallback, private val cameraStatusReceiver: ICameraStatusReceiver, private var sendRepeatCount: Int = 0)
17 {
18     companion object
19     {
20         private val TAG = SonySsdpClient::class.java.simpleName
21         private const val SEND_TIMES_DEFAULT = 3
22         private const val SEND_WAIT_DURATION_MS = 300
23         private const val SSDP_RECEIVE_TIMEOUT = 4 * 1000 // msec
24         private const val PACKET_BUFFER_SIZE = 4096
25         private const val SSDP_PORT = 1900
26         private const val SSDP_MX = 2
27         private const val SSDP_ADDR = "239.255.255.250"
28         private const val SSDP_ST = "urn:schemas-sony-com:service:ScalarWebAPI:1"
29         private const val SSDP_ST2 = "urn:schemas-upnp-org:service:ContentDirectory:1"
30         private const val SSDP_ST3 = "urn:schemas-upnp-org:device:MediaServer:1"
31     }
32
33     private val ssdpRequest: String
34     private val useSmartphoneTransfer = false
35
36     init
37     {
38         this.sendRepeatCount = if (sendRepeatCount > 0) sendRepeatCount else SEND_TIMES_DEFAULT
39         ssdpRequest = "M-SEARCH * HTTP/1.1\r\nHOST: $SSDP_ADDR:$SSDP_PORT\r\nMAN: \"ssdp:discover\"\r\nMX: $SSDP_MX\r\nST: $SSDP_ST\r\n\r\n"
40     }
41
42     fun search()
43     {
44         val sendData = ssdpRequest.toByteArray()
45         val detailString = ""
46         var socket: DatagramSocket? = null
47         var receivePacket: DatagramPacket
48         val packet: DatagramPacket
49
50         //  要求の送信
51         try
52         {
53             socket = DatagramSocket()
54             socket.reuseAddress = true
55             val iAddress = InetSocketAddress(SSDP_ADDR, SSDP_PORT)
56             packet = DatagramPacket(sendData, sendData.size, iAddress)
57
58             // 要求を繰り返し送信する
59             for (loop in 1..sendRepeatCount)
60             {
61                 cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CONNECT_CAMERA_SEARCH_REQUEST) + " " + loop)
62                 socket.send(packet)
63                 Thread.sleep(SEND_WAIT_DURATION_MS.toLong())
64             }
65             Log.v(TAG, " SSDP : SEND")
66         }
67         catch (e: Exception)
68         {
69             if (socket != null && !socket.isClosed)
70             {
71                 socket.close()
72             }
73             e.printStackTrace()
74
75             // エラー応答する
76             callback.onErrorFinished(detailString + " : " + e.localizedMessage)
77             return
78         }
79
80         // 応答の受信
81         val startTime = System.currentTimeMillis()
82         var currentTime = System.currentTimeMillis()
83         val foundDevices: MutableList<String?> = ArrayList()
84         val array = ByteArray(PACKET_BUFFER_SIZE)
85
86         try
87         {
88             cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CONNECT_WAIT_REPLY_CAMERA))
89             while (currentTime - startTime < SSDP_RECEIVE_TIMEOUT)
90             {
91                 receivePacket = DatagramPacket(array, array.size)
92                 socket.soTimeout = SSDP_RECEIVE_TIMEOUT
93                 socket.receive(receivePacket)
94                 val ssdpReplyMessage = String(receivePacket.getData(), 0, receivePacket.length, Charset.forName("UTF-8"))
95                 var ddUsn: String?
96                 if (ssdpReplyMessage.contains("HTTP/1.1 200"))
97                 {
98                     ddUsn = findParameterValue(ssdpReplyMessage, "USN")
99                     cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CONNECT_CAMERA_RECEIVED_REPLY))
100                     if (!foundDevices.contains(ddUsn))
101                     {
102                         val ddLocation = findParameterValue(ssdpReplyMessage, "LOCATION")
103                         foundDevices.add(ddUsn)
104
105                         //// Fetch Device Description XML and parse it.
106                         if (ddLocation != null)
107                         {
108                             cameraStatusReceiver.onStatusNotify("LOCATION : $ddLocation")
109                             val device: ISonyCamera? = searchSonyCameraDevice(ddLocation)
110                             if ((device != null)&&(device.hasApiService("camera")))
111                             {
112                                 cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CONNECT_CAMERA_FOUND) + " " + device.getFriendlyName())
113                                 callback.onDeviceFound(device)
114                                 // カメラが見つかった場合は breakする
115                                 break
116                             }
117                             else
118                             {
119                                 if (useSmartphoneTransfer && device != null)
120                                 {
121                                     cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CONNECT_CAMERA_FOUND) + " " + device.getFriendlyName() + " (Smartphone Transfer)")
122                                     Log.v(TAG, " SMARTPHONE TRANSFER : $ssdpReplyMessage")
123                                     callback.onDeviceFound(device)
124                                     break
125                                 }
126
127                                 // カメラが見つからない...
128                                 cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CAMERA_NOT_FOUND))
129                             }
130                         }
131                     }
132                     else
133                     {
134                         Log.v(TAG, "Already received. : $ddUsn")
135                     }
136                 }
137                 else
138                 {
139                     Log.v(TAG, " SSDP REPLY MESSAGE (ignored) : $ssdpReplyMessage")
140                 }
141                 currentTime = System.currentTimeMillis()
142             }
143         }
144         catch (e: Exception)
145         {
146             e.printStackTrace()
147
148             // エラー応答する
149             callback.onErrorFinished(detailString + " : " + e.localizedMessage)
150             return
151         }
152         finally
153         {
154             try
155             {
156                 if (!socket.isClosed())
157                 {
158                     socket.close()
159                 }
160             }
161             catch (ee: Exception)
162             {
163                 ee.printStackTrace()
164             }
165         }
166         callback.onFinished()
167     }
168
169     private fun findParameterValue(ssdpMessage: String, paramName: String): String?
170     {
171         var name = paramName
172         if (!name.endsWith(":"))
173         {
174             name = "$name:"
175         }
176         var start = ssdpMessage.indexOf(name)
177         val end = ssdpMessage.indexOf("\r\n", start)
178         if (start != -1 && end != -1)
179         {
180             start += name.length
181             try
182             {
183                 return ssdpMessage.substring(start, end).trim { it <= ' ' }
184             }
185             catch (e: Exception)
186             {
187                 e.printStackTrace()
188             }
189         }
190         return null
191     }
192
193     private fun searchSonyCameraDevice(ddUrl: String): ISonyCamera?
194     {
195         val httpClient = SimpleHttpClient()
196         var device: SonyCameraDeviceProvider? = null
197         val ddXml: String
198         try
199         {
200             ddXml = httpClient.httpGet(ddUrl, -1)
201             Log.d(TAG, "fetch () httpGet done. : " + ddXml.length)
202             if (ddXml.length < 2)
203             {
204                 // 内容がないときは...終了する
205                 Log.v(TAG, "NO BODY")
206                 return (null)
207             }
208         }
209         catch (e: java.lang.Exception)
210         {
211             e.printStackTrace()
212             return (null)
213         }
214         try
215         {
216             //Log.v(TAG, "ddXml : " + ddXml);
217             val rootElement = XmlElement.parse(ddXml)
218
219             // "root"
220             if ("root" == rootElement.tagName)
221             {
222                 // "device"
223                 val deviceElement = rootElement.findChild("device")
224                 val friendlyName = deviceElement.findChild("friendlyName").value
225                 val modelName = deviceElement.findChild("modelName").value
226                 val udn = deviceElement.findChild("UDN").value
227
228                 // "iconList"
229                 var iconUrl = ""
230                 val iconListElement = deviceElement.findChild("iconList")
231                 val iconElements = iconListElement.findChildren("icon")
232                 for (iconElement in iconElements) {
233                     // Choose png icon to show Android UI.
234                     if ("image/png" == iconElement.findChild("mimetype").value) {
235                         val uri = iconElement.findChild("url").value
236                         val hostUrl = toSchemeAndHost(ddUrl)
237                         iconUrl = hostUrl + uri
238                     }
239                 }
240                 device = SonyCameraDeviceProvider(ddUrl, friendlyName, modelName, udn, iconUrl)
241
242                 // "av:X_ScalarWebAPI_DeviceInfo"
243                 val wApiElement = deviceElement.findChild("X_ScalarWebAPI_DeviceInfo")
244                 val wApiServiceListElement = wApiElement.findChild("X_ScalarWebAPI_ServiceList")
245                 val wApiServiceElements =
246                     wApiServiceListElement.findChildren("X_ScalarWebAPI_Service")
247                 for (wApiServiceElement in wApiServiceElements) {
248                     val serviceName =
249                         wApiServiceElement.findChild("X_ScalarWebAPI_ServiceType").value
250                     val actionUrl =
251                         wApiServiceElement.findChild("X_ScalarWebAPI_ActionList_URL").value
252                     device.addApiService(serviceName, actionUrl)
253                 }
254             }
255         } catch (e: java.lang.Exception) {
256             e.printStackTrace()
257         }
258         Log.d(TAG, "fetch () parsing XML done.")
259         if (device == null) {
260             Log.v(TAG, "device is null.")
261         }
262         return device
263     }
264
265     private fun toSchemeAndHost(url: String): String {
266         val i = url.indexOf("://") // http:// or https://
267         if (i == -1) {
268             return ""
269         }
270         val j = url.indexOf("/", i + 3)
271         return if (j == -1) {
272             ""
273         } else url.substring(0, j)
274     }
275
276     private fun toHost(url: String): String
277     {
278         val i = url.indexOf("://") // http:// or https://
279         if (i == -1) {
280             return ""
281         }
282         val j = url.indexOf(":", i + 3)
283         return if (j == -1) {
284             ""
285         } else url.substring(i + 3, j)
286     }
287
288     /**
289      * 検索結果のコールバック
290      *
291      */
292     interface ISearchResultCallback
293     {
294         fun onDeviceFound(cameraDevice: ISonyCamera) // デバイスが見つかった!
295         fun onFinished() // 通常の終了をしたとき
296         fun onErrorFinished(reason: String?) // エラーが発生して応答したとき
297     }
298 }