Skip to content

Commit e2eddde

Browse files
authored
Merge pull request #1389 from PhenoApps/v7_canon_bugfixes
Canon Bug fix
2 parents b97f1df + 9e3e2c8 commit e2eddde

20 files changed

+604
-116
lines changed

app/src/main/java/com/fieldbook/tracker/adapters/ImageAdapter.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,19 @@ class ImageAdapter(private val context: Context, private val listener: ImageItem
107107
height = actualHeight
108108
}
109109

110-
val preview = if (model.uri?.contains("content://") != true) {
111-
if (model.uri != "NA") {
112-
labelView.text = model.uri
113-
labelView.visibility = View.VISIBLE
110+
val preview = try {
111+
if (model.uri?.contains("content://") != true) {
112+
if (model.uri != "NA") {
113+
labelView.text = model.uri
114+
labelView.visibility = View.VISIBLE
115+
}
116+
val data = context.resources.assets.open("na_placeholder.jpg").readBytes()
117+
BitmapFactory.decodeByteArray(data, 0, data.size)
118+
} else {
119+
BitmapLoader.getPreview(view.context, model.uri, model.orientation)
114120
}
115-
val data = context.resources.assets.open("na_placeholder.jpg").readBytes()
116-
BitmapFactory.decodeByteArray(data, 0, data.size)
117-
} else {
118-
BitmapLoader.getPreview(view.context, model.uri, model.orientation)
121+
} catch (_: Exception) {
122+
null
119123
}
120124

121125
imageView.setImageBitmap(preview)

app/src/main/java/com/fieldbook/tracker/devices/camera/CanonApi.kt

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package com.fieldbook.tracker.devices.camera
22

33
import android.content.Context
44
import android.graphics.BitmapFactory
5+
import android.net.Network
56
import android.util.Log
67
import androidx.preference.PreferenceManager
8+
import com.fieldbook.tracker.database.internalTimeFormatter
79
import com.fieldbook.tracker.devices.ptpip.ChannelBufferManager
810
import com.fieldbook.tracker.devices.ptpip.PtpOperations.Companion.writeOperation
911
import com.fieldbook.tracker.devices.ptpip.PtpSession
@@ -24,25 +26,30 @@ import com.fieldbook.tracker.devices.ptpip.toInt
2426
import com.fieldbook.tracker.objects.RangeObject
2527
import com.fieldbook.tracker.preferences.GeneralKeys
2628
import com.fieldbook.tracker.traits.AbstractCameraTrait
29+
import com.fieldbook.tracker.utilities.FileUtil
2730
import dagger.hilt.android.qualifiers.ActivityContext
2831
import kotlinx.coroutines.CoroutineScope
2932
import kotlinx.coroutines.Dispatchers
3033
import kotlinx.coroutines.launch
34+
import org.threeten.bp.OffsetDateTime
3135
import java.io.ByteArrayInputStream
3236
import java.net.InetSocketAddress
3337
import java.nio.ByteBuffer
3438
import java.nio.channels.SocketChannel
3539
import 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+
}
Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.fieldbook.tracker.devices.ptpip
22

3+
import java.io.EOFException
4+
import java.io.IOException
35
import java.nio.ByteBuffer
46
import java.nio.channels.SocketChannel
57

@@ -11,9 +13,21 @@ class ChannelBufferManager(val channel: SocketChannel) {
1113

1214
private val shortBuffer = ByteBuffer.allocate(Short.SIZE_BYTES)
1315

16+
// helper that ensures the provided ByteBuffer is completely filled from the channel
17+
@Throws(IOException::class)
18+
private fun readFully(buffer: ByteBuffer) {
19+
buffer.clear()
20+
while (buffer.hasRemaining()) {
21+
val read = channel.read(buffer)
22+
if (read == -1) throw EOFException("Unexpected EOF while reading from channel")
23+
}
24+
buffer.rewind()
25+
}
26+
27+
@Throws(IOException::class)
1428
fun getInt(): Int {
1529

16-
channel.read(intBuffer)
30+
readFully(intBuffer)
1731

1832
val value = intBuffer.toInt()
1933

@@ -22,9 +36,10 @@ class ChannelBufferManager(val channel: SocketChannel) {
2236
return value
2337
}
2438

39+
@Throws(IOException::class)
2540
fun getShort(): Int {
2641

27-
channel.read(shortBuffer)
42+
readFully(shortBuffer)
2843

2944
val value = shortBuffer.toInt()
3045

@@ -33,9 +48,10 @@ class ChannelBufferManager(val channel: SocketChannel) {
3348
return value
3449
}
3550

51+
@Throws(IOException::class)
3652
fun getLong(): Long {
3753

38-
channel.read(longBuffer)
54+
readFully(longBuffer)
3955

4056
val value = longBuffer.toLong()
4157

@@ -44,20 +60,22 @@ class ChannelBufferManager(val channel: SocketChannel) {
4460
return value
4561
}
4662

63+
@Throws(IOException::class)
4764
fun getBytes(length: Int): ByteArray {
4865

49-
val reader = channel.socket().getInputStream()
66+
if (length < 0) throw IOException("Negative length requested: $length")
5067

51-
val data = ByteArray(length)
52-
var index = 0
53-
while (index < length) {
68+
if (length == 0) return byteArrayOf()
5469

55-
val bytes = reader.read(data, index, length - index)
70+
val data = ByteArray(length)
71+
val buf = ByteBuffer.wrap(data)
5672

57-
index += bytes
73+
while (buf.hasRemaining()) {
74+
val read = channel.read(buf)
75+
if (read == -1) throw EOFException("Unexpected EOF while reading $length bytes from channel")
5876
}
5977

60-
return data.clone()
78+
return data
6179
}
6280

6381
}

0 commit comments

Comments
 (0)