OSDN Git Service

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