Generate SSD detection from typed Jerboa

ober

20ce4d4c89d1b15f956e0b5d7a5ddd923057a1eb

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 6617b91..ca33394 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -4699,6 +4699,7 @@
                       (java io OutputStream)
                       (java lang Runnable)
                       (java lang Thread)
+                      (java util UUID)
                       (android app AlertDialog)
                       (android app AlertDialog Builder)
                       (android content ContentResolver)
@@ -4834,6 +4835,7 @@
                 mainActivityLoadPdfAsync
                 mainActivityOpenPdfAsync
                 mainActivityShowSsdPdfBrowserAsync
+                mainActivityDetectCurrentAsync
                 mainActivityInstallApproveAreaDialogButton
                 mainActivityInstallAppendDialogButton
                 mainActivityInstallEditDialogButtons
@@ -4936,11 +4938,14 @@
         (type View)
         (type Window)
         (type DocumentFile)
+        (type NativeDetection)
+        (type OcrWord)
         (type JSONObject)
         (type ScaleGestureDetector)
         (type SimpleOnScaleGestureListener)
         (type SsdSession)
         (type SsdReviewView)
+        (type SsdCell)
         (type SsdPdfChoice)
         (type MainActivity)
         (type BoxType)
@@ -4948,9 +4953,14 @@
         (type TextView)
         (type TextWatcher)
         (type Toast)
+        (type UUID)
         (type ViewportState)
         (record BoxTypeDialogState
           ((mut previousBoxType : String)))
+        (record DetectionPreparation
+          ((ocrGuesses : Int32)
+           (detectorNote : String)
+           (detectorSource : String)))
         (extern (fileIsFile (file : File)) : Bool
           (kotlin-member-get isFile))
         (extern (fileName (file : File)) : String
@@ -4969,6 +4979,43 @@
           (kotlin-call sha1Hex))
         (extern (uiInputStreamCloseRaw (input : InputStream)) : Unit
           (kotlin-member-call close))
+        (extern (uuidRandomRaw) : UUID
+          (kotlin-call UUID randomUUID))
+        (extern (uuidToStringRaw (uuid : UUID)) : String
+          (kotlin-member-call toString))
+        (extern (uriToStringRaw (uri : Uri)) : String
+          (kotlin-member-call toString))
+        (extern (uiBitmapWidthRaw (bitmap : Bitmap)) : Int32
+          (kotlin-member-get width))
+        (extern (uiBitmapHeightRaw (bitmap : Bitmap)) : Int32
+          (kotlin-member-get height))
+        (extern (detectCellsRaw (bitmap : Bitmap)) : (MutableList SsdCell)
+          (kotlin-call detectCells))
+        (extern (ssdVisionDetectRawForUi
+                  (bitmap : Bitmap)) : (Nullable NativeDetection)
+          (kotlin-call SsdVisionBridge detect))
+        (extern (ssdVisionAvailableRawForUi) : Bool
+          (kotlin-call SsdVisionBridge isAvailable))
+        (extern (ssdVisionLoadErrorMessageRawForUi) : String
+          (kotlin-call SsdVisionBridge loadErrorMessage))
+        (extern (ssdOcrRecognizeWordsRawForUi
+                  (bitmap : Bitmap)) : (List OcrWord)
+          (kotlin-call SsdOcr recognizeWords))
+        (extern (applyInitialGuessesRawForUi
+                  (context : MainActivity)
+                  (session : SsdSession)) : Unit
+          (kotlin-call applyInitialGuesses))
+        (extern (applyDefaultOcrGuessesRawForUi
+                  (context : MainActivity)
+                  (session : SsdSession)) : Int32
+          (kotlin-call applyDefaultOcrGuesses))
+        (extern (detectFailedLogFieldsRawForUi
+                  (sourceKey : String)
+                  (forceDetect : Bool)
+                  (nativeAvailable : Bool)
+                  (nativeError : String)
+                  (error : JSONObject)) : JSONObject
+          (kotlin-call detectFailedLogFields))
         (extern (windowSetSoftInputMode (window : Window) (mode : Int32)) : Unit
           (kotlin-member-call setSoftInputMode))
         (extern (alertDialogIsShowing (dialog : AlertDialog)) : Bool
@@ -5508,6 +5555,10 @@
                   (learnedGuesses : Int32)
                   (ocrGuesses : Int32)) : Unit
           (kotlin-member-call logDetectResult))
+        (extern (mainActivityThrowableLogFromExceptionRaw
+                  (activity : MainActivity)
+                  (error : Exception)) : JSONObject
+          (kotlin-member-call throwableLog))
         (extern (mainActivityLoadPdfRaw
                   (activity : MainActivity)
                   (uri : Uri)
@@ -7246,6 +7297,340 @@
                               (exceptionMessageRaw error)))
                           (mainActivityChooseSsdPdfFolderRaw
                             activity))))))))))
+        (def (newDetectionSessionId) : String
+          (sessionIdFromUuid (uuidToStringRaw (uuidRandomRaw))))
+        (def (mainActivityDetectionSession
+               (bitmap : Bitmap)
+               (uri : Uri)
+               (expectedPdfSha1 : String)
+               (expectedPageIndex : Int32)
+               (expectedPdfName : String)
+               (expectedPageCount : Int32)
+               (dpi : Int32)) : SsdSession
+          (makeDetectionSession
+            (newDetectionSessionId)
+            (sourceKey expectedPdfSha1 (+ expectedPageIndex (int32 1)) dpi)
+            expectedPdfName
+            (uriToStringRaw uri)
+            (+ expectedPageIndex (int32 1))
+            expectedPageCount
+            dpi
+            (uiBitmapWidthRaw bitmap)
+            (uiBitmapHeightRaw bitmap)))
+        (def (mainActivityCurrentLoadStale
+               (activity : MainActivity)
+               (generation : Int32)
+               (uri : Uri)
+               (page : Int32)) : Bool
+          (currentLoadStale
+            generation
+            (mainActivityLoadGenerationRaw activity)
+            (mainActivityPdfUriRaw activity)
+            uri
+            (mainActivityPageIndexRaw activity)
+            page))
+        (def (mainActivityCurrentLoadMatches
+               (activity : MainActivity)
+               (generation : Int32)
+               (uri : Uri)
+               (page : Int32)) : Bool
+          (currentLoadMatches
+            generation
+            (mainActivityLoadGenerationRaw activity)
+            (mainActivityPdfUriRaw activity)
+            uri
+            (mainActivityPageIndexRaw activity)
+            page))
+        (def (mainActivityShowCachedTruthResult
+               (activity : MainActivity)
+               (view : SsdReviewView)
+               (store : TruthStore)
+               (session : SsdSession)
+               (generation : Int32)
+               (uri : Uri)
+               (page : Int32)) : Unit
+          (begin
+            (truthStoreApplyTruthAndPrune store session)
+            (mainActivityRunOnUiThreadRaw
+              activity
+              (lambda ()
+                (if (mainActivityCurrentLoadStale activity generation uri page)
+                  (begin)
+                  (begin
+                    (mainActivityShowSession activity view session)
+                    (mainActivitySetStatusRaw
+                      activity
+                      (sessionCountsStatus session ", replayed cached truth"))
+                    (mainActivityLogDetectResultRaw
+                      activity
+                      "cached-truth"
+                      session
+                      "local-truth"
+                      (int32 0)
+                      (int32 0))))))
+            (truthStorePullRemoteTruthCount store session)
+            (begin)))
+        (def (finishDetectionWithCells
+               (activity : MainActivity)
+               (store : TruthStore)
+               (session : SsdSession)
+               (bitmap : Bitmap)
+               (existingTruth : (Nullable JSONObject))
+               (existingTruthGroupCount : Int32)
+               (detectedCells : (List SsdCell))
+               (detectorNote : String)
+               (detectorSource : String)
+               (initialGroupLimit : Int32)) : DetectionPreparation
+          (begin
+            (ssdSessionAddDetectedCellsInsideArea session detectedCells)
+            (ssdSessionAddInitialGroups session initialGroupLimit)
+            (if (shouldApplyExistingTruthObject
+                  existingTruth
+                  existingTruthGroupCount)
+              (truthStoreApplyTruthAndPrune store session)
+              (begin))
+            (applyInitialGuessesRawForUi activity session)
+            (ssdSessionAddOcrWords
+              session
+              (ssdOcrRecognizeWordsRawForUi bitmap))
+            (make-DetectionPreparation
+              (applyDefaultOcrGuessesRawForUi activity session)
+              detectorNote
+              detectorSource)))
+        (def (prepareDetection
+               (activity : MainActivity)
+               (store : TruthStore)
+               (session : SsdSession)
+               (bitmap : Bitmap)
+               (existingTruth : (Nullable JSONObject))
+               (existingTruthGroupCount : Int32)
+               (forceDetect : Bool)
+               (initialGroupLimit : Int32)) : DetectionPreparation
+          (if (shouldUseExistingTruthObject
+                existingTruth
+                existingTruthGroupCount
+                forceDetect)
+            (begin
+              (copyExistingTruthSsdArea session existingTruth)
+              (truthStoreApplyTruthAndPrune store session)
+              (make-DetectionPreparation
+                (int32 0)
+                ""
+                "local-truth"))
+            (begin
+              (copyExistingTruthSsdArea session existingTruth)
+              (let ((nativeDetection (ssdVisionDetectRawForUi bitmap)))
+                (if (nativeDetectionPresent nativeDetection)
+                  (finishDetectionWithCells
+                    activity
+                    store
+                    session
+                    bitmap
+                    existingTruth
+                    existingTruthGroupCount
+                    (NativeDetection-cells
+                      (nativeDetectionOrEmpty nativeDetection))
+                    ", rust detector"
+                    (detectorSourceWithTruth
+                      forceDetect
+                      existingTruthGroupCount
+                      "forced-rust-plus-truth"
+                      "rust-detector")
+                    initialGroupLimit)
+                  (finishDetectionWithCells
+                    activity
+                    store
+                    session
+                    bitmap
+                    existingTruth
+                    existingTruthGroupCount
+                    (detectCellsRaw bitmap)
+                    ""
+                    (detectorSourceWithTruth
+                      forceDetect
+                      existingTruthGroupCount
+                      "forced-android-plus-truth"
+                      "android-fallback")
+                    initialGroupLimit))))))
+        (def (mainActivityShowDetectionResult
+               (activity : MainActivity)
+               (view : SsdReviewView)
+               (store : TruthStore)
+               (session : SsdSession)
+               (existingTruth : (Nullable JSONObject))
+               (forceDetect : Bool)
+               (preparation : DetectionPreparation)
+               (learnedGuesses : Int32)
+               (generation : Int32)
+               (uri : Uri)
+               (page : Int32)) : Unit
+          (mainActivityRunOnUiThreadRaw
+            activity
+            (lambda ()
+              (if (mainActivityCurrentLoadStale activity generation uri page)
+                (begin)
+                (begin
+                  (mainActivityShowSession activity view session)
+                  (mainActivitySetStatusRaw
+                    activity
+                    (sessionCountsStatus
+                      session
+                      (string-append
+                        (DetectionPreparation-detectorNote preparation)
+                        (string-append
+                          (detectionTruthNoteFor existingTruth)
+                          (string-append
+                            (detectionOcrNote
+                              (DetectionPreparation-ocrGuesses preparation)
+                              (list-size (SsdSession-ocrWords session)))
+                            (detectionLearnedNote learnedGuesses))))))
+                  (mainActivityLogDetectResultRaw
+                    activity
+                    "page-load"
+                    session
+                    (DetectionPreparation-detectorSource preparation)
+                    learnedGuesses
+                    (DetectionPreparation-ocrGuesses preparation))
+                  (if (shouldMergeRemoteTruthObject
+                        existingTruth
+                        forceDetect)
+                    (mainActivityMergeRemoteTruthAsync
+                      activity
+                      view
+                      store
+                      session
+                      generation
+                      uri
+                      page)
+                    (begin)))))))
+        (def (mainActivityHandleDetectFailure
+               (activity : MainActivity)
+               (error : Exception)
+               (expectedPdfSha1 : String)
+               (expectedPageIndex : Int32)
+               (forceDetect : Bool)
+               (dpi : Int32)
+               (readTimeoutMillis : Int32)) : Unit
+          (mainActivityLogClientEventAsync
+            activity
+            "ssd-detect-failed"
+            (detectFailedLogFieldsRawForUi
+              (sourceKey expectedPdfSha1 (+ expectedPageIndex (int32 1)) dpi)
+              forceDetect
+              (ssdVisionAvailableRawForUi)
+              (ssdVisionLoadErrorMessageRawForUi)
+              (mainActivityThrowableLogFromExceptionRaw activity error))
+            readTimeoutMillis))
+        (def (mainActivityDetectCurrentAsync
+               (activity : MainActivity)
+               (view : SsdReviewView)
+               (store : TruthStore)
+               (bitmap : Bitmap)
+               (uri : Uri)
+               (forceDetect : Bool)
+               (expectedGeneration : Int32)
+               (expectedPageIndex : Int32)
+               (expectedPdfName : String)
+               (expectedPdfSha1 : String)
+               (expectedPageCount : Int32)
+               (dpi : Int32)
+               (fastTruthGroupThreshold : Int32)
+               (initialGroupLimit : Int32)
+               (clientLogReadTimeoutMillis : Int32)) : Unit
+          (begin
+            (mainActivitySetStatusRaw activity (detectStartStatus forceDetect))
+            (runBackground
+              (lambda ()
+                (try
+                  (if (mainActivityCurrentLoadStale
+                        activity
+                        expectedGeneration
+                        uri
+                        expectedPageIndex)
+                    (begin)
+                    (let ((session
+                            (mainActivityDetectionSession
+                              bitmap
+                              uri
+                              expectedPdfSha1
+                              expectedPageIndex
+                              expectedPdfName
+                              expectedPageCount
+                              dpi)))
+                      (let ((localTruth
+                              (localTruthForDetection
+                                store
+                                session
+                                forceDetect)))
+                        (let ((localGroupCount
+                                (nullableTruthGroupCount localTruth)))
+                          (if (truthGroupCountAtLeast
+                                localGroupCount
+                                fastTruthGroupThreshold)
+                            (mainActivityShowCachedTruthResult
+                              activity
+                              view
+                              store
+                              session
+                              expectedGeneration
+                              uri
+                              expectedPageIndex)
+                            (let ((existingTruth
+                                    (existingTruthForDetection
+                                      store
+                                      session
+                                      localTruth)))
+                              (let ((existingTruthGroupCount
+                                      (nullableTruthGroupCount
+                                        existingTruth)))
+                                (let ((preparation
+                                        (prepareDetection
+                                          activity
+                                          store
+                                          session
+                                          bitmap
+                                          existingTruth
+                                          existingTruthGroupCount
+                                          forceDetect
+                                          initialGroupLimit)))
+                                  (mainActivityShowDetectionResult
+                                    activity
+                                    view
+                                    store
+                                    session
+                                    existingTruth
+                                    forceDetect
+                                    preparation
+                                    (learnedGuessesForDetection
+                                      store
+                                      session
+                                      existingTruthGroupCount)
+                                    expectedGeneration
+                                    uri
+                                    expectedPageIndex)))))))))
+                  (catch (error : Exception)
+                    (mainActivityRunOnUiThreadRaw
+                      activity
+                      (lambda ()
+                        (if (mainActivityCurrentLoadMatches
+                              activity
+                              expectedGeneration
+                              uri
+                              expectedPageIndex)
+                          (begin
+                            (mainActivitySetStatusRaw
+                              activity
+                              (detectFailedStatus
+                                (exceptionMessageRaw error)))
+                            (mainActivityHandleDetectFailure
+                              activity
+                              error
+                              expectedPdfSha1
+                              expectedPageIndex
+                              forceDetect
+                              dpi
+                              clientLogReadTimeoutMillis))
+                          (begin))))))))))
         (def (mainActivityInstallApproveAreaDialogButton
                (activity : MainActivity)
                (dialog : AlertDialog)
@@ -9287,99 +9672,23 @@
        "        val bitmap = checkNotNull(currentBitmap)"
        "        if (!pdfUriLoaded(expectedUri)) return"
        "        val uri = uriOrEmpty(expectedUri)"
-       "        setStatus(detectStartStatus(forceDetect))"
-       "        thread {"
-       "            try {"
-       "                if (isStaleLoad(expectedGeneration, uri, expectedPageIndex)) return@thread"
-       "                val key = sourceKey(expectedPdfSha1, expectedPageIndex + 1, DPI)"
-       "                val session = makeDetectionSession("
-       "                    sessionIdFromUuid(UUID.randomUUID().toString()),"
-       "                    key,"
-       "                    expectedPdfName,"
-       "                    uri.toString(),"
-       "                    expectedPageIndex + 1,"
-       "                    expectedPageCount,"
-       "                    DPI,"
-       "                    bitmap.width,"
-       "                    bitmap.height"
-       "                )"
-       ""
-       "                val localTruth = localTruthForDetection(truthStore, session, forceDetect)"
-	       "                val localGroupCount = nullableTruthGroupCount(localTruth)"
-	       "                if (truthGroupCountAtLeast(localGroupCount, FAST_TRUTH_GROUP_THRESHOLD)) {"
-	       "                    truthStoreApplyTruthAndPrune(truthStore, session)"
-	       "                    runOnUiThread {"
-       "                        if (isStaleLoad(expectedGeneration, uri, expectedPageIndex)) return@runOnUiThread"
-	       "                        mainActivityShowSession(this, reviewView, session)"
-	       "                        setStatus(sessionCountsStatus(session, \", replayed cached truth\"))"
-	       "                        logDetectResult(\"cached-truth\", session, \"local-truth\")"
-       "                    }"
-       "                    truthStorePullRemoteTruthCount(truthStore, session)"
-       "                    return@thread"
-       "                }"
-       ""
-       "                val remoteMatches = 0"
-       "                val existingTruth = existingTruthForDetection(truthStore, session, localTruth)"
-       "                val existingTruthGroupCount = nullableTruthGroupCount(existingTruth)"
-       "                var ocrGuesses = 0"
-       "                var detectorNote = \"\""
-       "                var detectorSource = \"unknown\""
-       ""
-       "                if (shouldUseExistingTruthObject(existingTruth, existingTruthGroupCount, forceDetect)) {"
-       "                    detectorSource = \"local-truth\""
-       "                    copyExistingTruthSsdArea(session, existingTruth)"
-       "                    truthStoreApplyTruthAndPrune(truthStore, session)"
-       "                } else {"
-       "                    copyExistingTruthSsdArea(session, existingTruth)"
-       "                    val nativeDetection = ssdVisionDetect(bitmap)"
-       "                    val detectedCells: List<SsdCell>"
-       "                    if (nativeDetectionPresent(nativeDetection)) {"
-       "                        val rustDetection = nativeDetectionOrEmpty(nativeDetection)"
-       "                        detectedCells = rustDetection.cells"
-       "                        detectorNote = \", rust detector\""
-       "                        detectorSource = detectorSourceWithTruth(forceDetect, existingTruthGroupCount, \"forced-rust-plus-truth\", \"rust-detector\")"
-       "                    } else {"
-       "                        detectedCells = detectCells(bitmap)"
-       "                        detectorSource = detectorSourceWithTruth(forceDetect, existingTruthGroupCount, \"forced-android-plus-truth\", \"android-fallback\")"
-       "                    }"
-       "                    ssdSessionAddDetectedCellsInsideArea(session, detectedCells)"
-       "                    ssdSessionAddInitialGroups(session, 120)"
-       "                    if (shouldApplyExistingTruthObject(existingTruth, existingTruthGroupCount)) {"
-       "                        truthStoreApplyTruthAndPrune(truthStore, session)"
-       "                    }"
-       "                    applyInitialGuesses(this, session)"
-       "                    ssdSessionAddOcrWords(session, ssdOcrRecognizeWords(bitmap))"
-       "                    ocrGuesses = applyDefaultOcrGuesses(this, session)"
-       "                }"
-       "                val learnedGuesses = learnedGuessesForDetection(truthStore, session, existingTruthGroupCount)"
-       "                runOnUiThread {"
-       "                    if (isStaleLoad(expectedGeneration, uri, expectedPageIndex)) return@runOnUiThread"
-	       "                    mainActivityShowSession(this, reviewView, session)"
-       "                    val truthNote = detectionTruthNoteFor(existingTruth)"
-       "                    val ocrNote = detectionOcrNote(ocrGuesses, session.ocrWords.size)"
-       "                    val learnedNote = detectionLearnedNote(learnedGuesses)"
-       "                    setStatus(sessionCountsStatus(session, detectorNote + truthNote + ocrNote + learnedNote))"
-       "                    logDetectResult(\"page-load\", session, detectorSource, learnedGuesses, ocrGuesses)"
-       "                    if (shouldMergeRemoteTruthObject(existingTruth, forceDetect)) mergeRemoteTruthAsync(session, expectedGeneration, uri, expectedPageIndex)"
-       "                }"
-       "            } catch (error: Exception) {"
-       "                runOnUiThread {"
-       "                    if (isCurrentLoad(expectedGeneration, uri, expectedPageIndex)) {"
-       "                        setStatus(detectFailedStatus(error.message))"
-       "                        logClientEvent("
-       "                            \"ssd-detect-failed\","
-       "                            detectFailedLogFields("
-       "                                sourceKey(expectedPdfSha1, expectedPageIndex + 1, DPI),"
-       "                                forceDetect,"
-       "                                ssdVisionAvailable(),"
-       "                                ssdVisionLoadErrorMessage(),"
-       "                                throwableLog(error)"
-       "                            )"
-       "                        )"
-       "                    }"
-       "                }"
-       "            }"
-       "        }"
+       "        mainActivityDetectCurrentAsync("
+       "            this,"
+       "            reviewView,"
+       "            truthStore,"
+       "            bitmap,"
+       "            uri,"
+       "            forceDetect,"
+       "            expectedGeneration,"
+       "            expectedPageIndex,"
+       "            expectedPdfName,"
+       "            expectedPdfSha1,"
+       "            expectedPageCount,"
+       "            DPI,"
+       "            FAST_TRUTH_GROUP_THRESHOLD,"
+       "            120,"
+       "            CLIENT_LOG_READ_TIMEOUT_MS"
+       "        )"
        "    }"
        ""
        "    private fun mergeRemoteTruthAsync(session: SsdSession, generation: Int, uri: Uri, page: Int) {"