diff --git a/myscript-iink/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/InputController.java b/myscript-iink/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/InputController.java index d732d66..2298035 100644 --- a/myscript-iink/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/InputController.java +++ b/myscript-iink/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/InputController.java @@ -3,6 +3,7 @@ package com.myscript.iink.uireferenceimplementation; import android.content.Context; +import android.os.Build; import android.os.SystemClock; import android.util.Log; import android.view.GestureDetector; @@ -188,7 +189,14 @@ else if (inputMode == INPUT_MODE_FORCE_TOUCH) ToolController toolController = editor.getToolController(); PointerTool tool = toolController.getToolForType(iinkPointerType); if (tool == PointerTool.PEN || tool == PointerTool.HIGHLIGHTER) - editorView.requestUnbufferedDispatch(event); + { + // API 27/28/29 의 ViewRootImpl 은 "즉시 배치 소비"(scheduleConsumeBatchedInputImmediately)가 + // 예약된 프레임 소비를 취소만 하고 실제로는 아무것도 소비하지 않는다. 그 결과 배치된 입력이 + // 영영 회수되지 않아 입력 디스패치 타임아웃 ANR 이 발생한다. API 30 에서 수정됨. + // → trouble-shootings/myscript-iink-4.5-anr-rollback.md + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + editorView.requestUnbufferedDispatch(event); + } try { diff --git a/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/Expression.kt b/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/Expression.kt index 2a567dc..f7088e3 100644 --- a/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/Expression.kt +++ b/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/Expression.kt @@ -14,7 +14,15 @@ data class Expression( @SerializedName("bounding-box") val boundingBox: BoundingBox?, val symbols: List?, - val operands: List? + /** + * iink 4.5 부터 **미해결 피연산자 자리**(예: `2+` 처럼 한쪽을 비운 입력)를 + * 객체가 아닌 `null` 로 직렬화한다. 3.0.2 는 같은 자리를 + * `{"type":"number","label":"?","generated":true,"error":"Unsolved"}` 객체로 내보냈다. + * + * Gson 은 Kotlin 의 non-null 제네릭을 강제하지 않아 타입 선언만으로는 막을 수 없으므로, + * 원소 타입을 nullable 로 선언하고 순회하는 쪽에서 걸러낸다. + */ + val operands: List? ) internal fun Expression.changeItem(newItem: Item): Expression = @@ -23,6 +31,6 @@ internal fun Expression.changeItem(newItem: Item): Expression = if (item.id == newItem.id) newItem else item }, operands = operands?.map { operand -> - operand.changeItem(newItem) + operand?.changeItem(newItem) } ) diff --git a/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/Jiix.kt b/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/Jiix.kt index 5d89aab..d1a1162 100644 --- a/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/Jiix.kt +++ b/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/Jiix.kt @@ -7,7 +7,8 @@ data class Jiix( val id: String, val type: String, val items: List?, - val expressions: List?, + /** 원소가 null 일 수 있는 이유는 [Expression.operands] 주석 참고. */ + val expressions: List?, @SerializedName("bounding-box") val boundingBox: BoundingBox?, val version: String @@ -15,7 +16,7 @@ data class Jiix( fun Jiix.isValid() = !expressions.isNullOrEmpty() && - expressions.all { it.id != null } && + expressions.all { it?.id != null } && getAllItems().all { it.isValid } fun Jiix.changeItem(itemId: String, func: Item.() -> Item) = @@ -27,6 +28,6 @@ internal fun Jiix.changeItem(newItem: Item) = if (item.id == newItem.id) newItem else item }, expressions = expressions?.map { expression -> - expression.changeItem(newItem) + expression?.changeItem(newItem) } ) diff --git a/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/JiixExtension.kt b/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/JiixExtension.kt index 4f4a7fc..2dc0144 100644 --- a/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/JiixExtension.kt +++ b/myscript-iink/src/main/java/com/knowre/android/myscript/iink/jiix/JiixExtension.kt @@ -8,9 +8,9 @@ import java.util.Collections //region Jiix internal fun Jiix.getAllExpressions(): List { fun List.flatten(): List = - this + this.flatMap { it.operands?.flatten() ?: Collections.emptyList() } + this + this.flatMap { it.operands?.filterNotNull()?.flatten() ?: Collections.emptyList() } - return this.expressions?.flatten() ?: Collections.emptyList() + return this.expressions?.filterNotNull()?.flatten() ?: Collections.emptyList() } fun Jiix.getCandidates(item: Item): List {