@@ -2,8 +2,10 @@ package com.fieldbook.tracker.devices.camera
22
33import android.content.Context
44import android.graphics.BitmapFactory
5+ import android.net.Network
56import android.util.Log
67import androidx.preference.PreferenceManager
8+ import com.fieldbook.tracker.database.internalTimeFormatter
79import com.fieldbook.tracker.devices.ptpip.ChannelBufferManager
810import com.fieldbook.tracker.devices.ptpip.PtpOperations.Companion.writeOperation
911import com.fieldbook.tracker.devices.ptpip.PtpSession
@@ -24,25 +26,30 @@ import com.fieldbook.tracker.devices.ptpip.toInt
2426import com.fieldbook.tracker.objects.RangeObject
2527import com.fieldbook.tracker.preferences.GeneralKeys
2628import com.fieldbook.tracker.traits.AbstractCameraTrait
29+ import com.fieldbook.tracker.utilities.FileUtil
2730import dagger.hilt.android.qualifiers.ActivityContext
2831import kotlinx.coroutines.CoroutineScope
2932import kotlinx.coroutines.Dispatchers
3033import kotlinx.coroutines.launch
34+ import org.threeten.bp.OffsetDateTime
3135import java.io.ByteArrayInputStream
3236import java.net.InetSocketAddress
3337import java.nio.ByteBuffer
3438import java.nio.channels.SocketChannel
3539import javax.inject.Inject
3640
3741
38- class CanonApi @Inject constructor(@ActivityContext private val context : Context ) {
42+ class CanonApi @Inject constructor(@param: ActivityContext private val context : Context ) {
3943
4044 companion object {
4145 const val TAG = " CanonAPI"
4246 const val HANDLE_UPDATE_FRAME_COUNT = 30
4347 const val PHONE_NAME = " Field Book"
4448 }
4549
50+ @Volatile
51+ var boundNetwork: Network ? = null
52+
4653 var isConnected = false
4754
4855 private var obsUnit: RangeObject ? = null
@@ -64,7 +71,7 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
6471 }
6572
6673 private val isDebug by lazy {
67- prefs.getBoolean(GeneralKeys .CANON_DEBUG , false )
74+ true // prefs.getBoolean(GeneralKeys.CANON_DEBUG, true )
6875 }
6976
7077 private fun log (message : String ) {
@@ -277,6 +284,10 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
277284
278285 writeParameter3(session, storageId)
279286
287+ } else {
288+
289+ writeParameter902f(session, storageId)
290+
280291 }
281292 }
282293 }
@@ -301,11 +312,8 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
301312
302313 log(" d246 response $response " )
303314
304- if (response) {
305-
306- writeParameter902f(session, storageId)
315+ writeParameter902f(session, storageId)
307316
308- }
309317 }
310318 }
311319
@@ -335,7 +343,22 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
335343 private fun startNewSession (sessionCallback : PtpSessionCallback ) {
336344
337345 // TODO potentially add these timeouts to preferences
338- val command: SocketChannel = SocketChannel .open(InetSocketAddress (canonIp, canonPort))
346+ // open and bind the command channel to the selected network when available
347+ val command: SocketChannel = try {
348+ val address = InetSocketAddress (canonIp, canonPort)
349+ if (boundNetwork != null ) {
350+ val ch = SocketChannel .open()
351+ // Bind the socket to the network so traffic goes over the chosen network
352+ boundNetwork!! .bindSocket(ch.socket())
353+ ch.connect(address)
354+ ch
355+ } else {
356+ SocketChannel .open(address)
357+ }
358+ } catch (e: Exception ) {
359+ e.printStackTrace()
360+ SocketChannel .open(InetSocketAddress (canonIp, canonPort))
361+ }
339362 command.socket().soTimeout = 10000
340363 command.socket().keepAlive = true
341364 command.socket().tcpNoDelay = true
@@ -345,7 +368,20 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
345368
346369 val connectionNumber = requestConnection(chanMan)
347370
348- val events = SocketChannel .open(InetSocketAddress (canonIp, canonPort))
371+ val events: SocketChannel = try {
372+ val address = InetSocketAddress (canonIp, canonPort)
373+ if (boundNetwork != null ) {
374+ val ch = SocketChannel .open()
375+ boundNetwork!! .bindSocket(ch.socket())
376+ ch.connect(address)
377+ ch
378+ } else {
379+ SocketChannel .open(address)
380+ }
381+ } catch (e: Exception ) {
382+ e.printStackTrace()
383+ SocketChannel .open(InetSocketAddress (canonIp, canonPort))
384+ }
349385 events.socket().soTimeout = 120000
350386 events.socket().keepAlive = true
351387 events.socket().tcpNoDelay = true
@@ -401,7 +437,10 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
401437
402438 var payloadSize = 0
403439
404- var nextData: ByteArray = byteArrayOf()
440+ val initialOffset = offset ? : 0
441+ var currentOffset = initialOffset
442+
443+ var isFirstChunk = true
405444
406445 session.transaction({ tid ->
407446
@@ -415,7 +454,23 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
415454
416455 val (packetType, nd) = awaitImageEndPacket(session)
417456
418- nextData + = nd
457+ // Stream each chunk immediately to callbacks so we don't accumulate them in memory
458+ try {
459+ session.callbacks?.onJpegCaptured(
460+ nd,
461+ unit,
462+ saveTime,
463+ saveState = if (isFirstChunk && initialOffset == 0 ) AbstractCameraTrait .SaveState .NEW else AbstractCameraTrait .SaveState .SAVING ,
464+ offset = currentOffset
465+ )
466+ } catch (e: Exception ) {
467+ log(" Error invoking onJpegCaptured for chunk at offset=$currentOffset : ${e.message} " )
468+ }
469+
470+ isFirstChunk = false
471+
472+ // advance offset by the requested chunk length
473+ currentOffset + = length
419474
420475 if (packetType == PACKET_TYPE_END_DATA ) {
421476
@@ -429,13 +484,18 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
429484
430485 if (payloadSize == length) {
431486
432- session.callbacks?.onJpegCaptured(nextData, unit, saveTime,
433- saveState = if (offset == 0 ) AbstractCameraTrait .SaveState .NEW else AbstractCameraTrait .SaveState .SAVING ,
434- offset = offset ? : 0 )
435-
436487 Log .d(TAG , " Getting offset: ${offset ? : 0 } for ${unit.uniqueId} " )
437488 requestGetImage(session, handle, unit, (offset ? : 0 ) + length, saveTime)
438489
490+ } else {
491+ // No more full-size chunks; signal COMPLETE so the assembled temp file can be written to destination.
492+ session.callbacks?.onJpegCaptured(
493+ byteArrayOf(),
494+ unit,
495+ saveTime,
496+ AbstractCameraTrait .SaveState .COMPLETE ,
497+ offset = 0
498+ )
439499 }
440500 }
441501 }
@@ -467,7 +527,7 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
467527
468528 log(" REQUESTING UPDATES" )
469529
470- requestUpdateHandles(session, storageId, lastSaveTime )
530+ requestUpdateHandles(session, storageId)
471531
472532 } else {
473533
@@ -484,7 +544,7 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
484544 private fun requestUpdateHandles (
485545 session : PtpSession ,
486546 storageId : ByteArray ,
487- lastSavedTime : String? = null
547+ lastSavedTime : String? = FileUtil .sanitizeFileName( OffsetDateTime .now().format(internalTimeFormatter))
488548 ) {
489549
490550 log(" Requesting update image handles" )
@@ -521,15 +581,8 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
521581
522582 // requestUiLock(true, session, storageId, unit)
523583
584+ // requestGetImage will stream NEW/SAVING chunks and will send COMPLETE
524585 requestGetImage(session, handle, unit, saveTime = time)
525-
526- session.callbacks?.onJpegCaptured(
527- byteArrayOf(),
528- unit,
529- time,
530- AbstractCameraTrait .SaveState .COMPLETE ,
531- offset = 0
532- )
533586 }
534587 }
535588 }
@@ -840,7 +893,7 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
840893
841894 val numStorageIds = session.comMan.getInt()
842895
843- for (i in 0 .. < numStorageIds) {
896+ for (i in 0 until numStorageIds) {
844897
845898 handles.add(session.comMan.getInt().toByteArray())
846899
@@ -861,7 +914,7 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
861914
862915 val numStorageIds = session.comMan.getInt()
863916
864- for (i in 0 .. < numStorageIds) {
917+ for (i in 0 until numStorageIds) {
865918
866919 handles.add(session.comMan.getInt().toByteArray())
867920
@@ -984,4 +1037,4 @@ class CanonApi @Inject constructor(@ActivityContext private val context: Context
9841037
9851038 return packetTypeInt to data
9861039 }
987- }
1040+ }
0 commit comments