From 9d8022a01627b8057298a9a3bac1af7e3e89d924 Mon Sep 17 00:00:00 2001 From: Victor Carreras <34163765+vicajilau@users.noreply.github.com> Date: Thu, 9 Jul 2026 18:14:26 +0200 Subject: [PATCH 1/2] feat: remove HMS support, update to Gradle 9, and migrate to type-safe BundleCompat for Android 15 compatibility --- CHANGELOG.md | 7 +- android/build.gradle.kts | 17 +--- .../CunningDocumentScannerPlugin.kt | 87 ++++++++----------- .../fallback/DocumentScannerActivity.kt | 36 +------- .../fallback/HmsEdgeDetector.kt | 63 -------------- .../gradle/wrapper/gradle-wrapper.properties | 6 +- example/android/settings.gradle.kts | 4 +- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 9 files changed, 51 insertions(+), 173 deletions(-) delete mode 100644 android/src/main/kotlin/biz/cunning/cunning_document_scanner/fallback/HmsEdgeDetector.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 76d9e00..a846d98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ -## 2.5.1 +## 2.6.0 ### Android -* Made HMS ML Kit dependencies optional using `compileOnly` and dynamic reflection to resolve `allowBackup` manifest merger conflicts (fixes #144). -* Prevented bundling of Huawei HMS libraries in the final APK by default, reducing build sizes for non-HMS projects. +* Removed HMS (Huawei Mobile Services) support entirely to ensure 16 KB page-size compatibility on Android 15+ (fixes #146). +* Replaced deprecated `getParcelable(key)` with type-safe `androidx.core.os.BundleCompat.getParcelable` for Android 13+ compatibility. +* Migrated the legacy `android` block to the modern `configure` block and updated conditional Kotlin plugin application in `build.gradle.kts` to resolve build/deprecation warnings. ## 2.5.0 ### General diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 89f8d98..bea60df 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,4 +1,5 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import com.android.build.api.dsl.LibraryExtension group = "biz.cunning.cunning_document_scanner" version = "1.0-SNAPSHOT" @@ -9,18 +10,10 @@ plugins { val agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.substringBefore('.').toInt() if (agpMajor < 9) { - apply(plugin = "org.jetbrains.kotlin.android") + pluginManager.apply("org.jetbrains.kotlin.android") } -rootProject.allprojects { - repositories { - maven { - url = uri("https://developer.huawei.com/repo/") - } - } -} - -android { +configure { namespace = "biz.cunning.cunning_document_scanner" compileSdk = 34 @@ -42,13 +35,11 @@ android { plugins.withId("org.jetbrains.kotlin.android") { extensions.configure { compilerOptions { - jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + jvmTarget.set(JvmTarget.JVM_17) } } } dependencies { implementation("com.google.android.gms:play-services-mlkit-document-scanner:16.0.0") - compileOnly("com.huawei.hms:ml-computer-vision-documentskew:3.11.0.301") - compileOnly("com.huawei.hms:ml-computer-vision-documentskew-model:3.7.0.301") } \ No newline at end of file diff --git a/android/src/main/kotlin/biz/cunning/cunning_document_scanner/CunningDocumentScannerPlugin.kt b/android/src/main/kotlin/biz/cunning/cunning_document_scanner/CunningDocumentScannerPlugin.kt index 252ecc7..65c6a4c 100644 --- a/android/src/main/kotlin/biz/cunning/cunning_document_scanner/CunningDocumentScannerPlugin.kt +++ b/android/src/main/kotlin/biz/cunning/cunning_document_scanner/CunningDocumentScannerPlugin.kt @@ -9,8 +9,6 @@ import androidx.core.app.ActivityCompat import biz.cunning.cunning_document_scanner.fallback.DocumentScannerActivity import biz.cunning.cunning_document_scanner.fallback.constants.DocumentScannerExtra import biz.cunning.cunning_document_scanner.fallback.utils.FileUtil -import java.io.File -import com.google.mlkit.common.MlKitException import com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions import com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions.RESULT_FORMAT_JPEG import com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions.RESULT_FORMAT_PDF @@ -92,7 +90,7 @@ class CunningDocumentScannerPlugin : FlutterPlugin, MethodCallHandler, ActivityA } try { ActivityCompat.startActivityForResult(activity, intent, START_GALLERY_PICKER, null) - } catch (e: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { pendingResult?.error("ERROR", "Failed to start gallery picker", null) } } @@ -153,10 +151,14 @@ class CunningDocumentScannerPlugin : FlutterPlugin, MethodCallHandler, ActivityA if (error != null) { pendingResult?.error("ERROR", "error - $error", null) } else { - // get an array with scanned document file paths - val scanningResult: GmsDocumentScanningResult = - data?.extras?.getParcelable("extra_scanning_result") - ?: return@ActivityResultListener false + // get an array with scanned document file paths + val scanningResult = data?.extras?.let { extras -> + androidx.core.os.BundleCompat.getParcelable( + extras, + "extra_scanning_result", + GmsDocumentScanningResult::class.java + ) + } ?: return@ActivityResultListener false if (asPdf) { val pdfUri = scanningResult.pdf?.uri?.toString()?.removePrefix("file://") @@ -281,31 +283,7 @@ class CunningDocumentScannerPlugin : FlutterPlugin, MethodCallHandler, ActivityA } } - private fun isHmsAvailable(context: android.content.Context): Boolean { - return try { - context.packageManager.getPackageInfo("com.huawei.hwid", 0) - true - } catch (e: android.content.pm.PackageManager.NameNotFoundException) { - false - } - } - private fun startScan(noOfPages: Int, isGalleryImportAllowed: Boolean, scannerMode: Int, asPdf: Boolean) { - if (isHmsAvailable(activity)) { - val intent = createDocumentScanIntent(noOfPages) - try { - ActivityCompat.startActivityForResult( - this.activity, - intent, - START_DOCUMENT_FB_ACTIVITY, - null - ) - } catch (e: ActivityNotFoundException) { - pendingResult?.error("ERROR", "FAILED TO START ACTIVITY", null) - } - return - } - val optionsBuilder = GmsDocumentScannerOptions.Builder() .setGalleryImportAllowed(isGalleryImportAllowed) .setPageLimit(noOfPages) @@ -318,31 +296,34 @@ class CunningDocumentScannerPlugin : FlutterPlugin, MethodCallHandler, ActivityA } val options = optionsBuilder.build() - val scanner = GmsDocumentScanning.getClient(options) - scanner.getStartScanIntent(activity).addOnSuccessListener { - try { - // Use a custom request code for onActivityResult identification - activity.startIntentSenderForResult(it, START_DOCUMENT_ACTIVITY, null, 0, 0, 0) - - } catch (e: IntentSender.SendIntentException) { - pendingResult?.error("ERROR", "Failed to start document scanner", null) - } - }.addOnFailureListener { - if (it is MlKitException) { - val intent = createDocumentScanIntent(noOfPages) + try { + val scanner = GmsDocumentScanning.getClient(options) + scanner.getStartScanIntent(activity).addOnSuccessListener { try { - ActivityCompat.startActivityForResult( - this.activity, - intent, - START_DOCUMENT_FB_ACTIVITY, - null - ) - } catch (e: ActivityNotFoundException) { - pendingResult?.error("ERROR", "FAILED TO START ACTIVITY", null) + // Use a custom request code for onActivityResult identification + activity.startIntentSenderForResult(it, START_DOCUMENT_ACTIVITY, null, 0, 0, 0) + } catch (_: IntentSender.SendIntentException) { + pendingResult?.error("ERROR", "Failed to start document scanner", null) } - } else { - pendingResult?.error("ERROR", "Failed to start document scanner Intent", null) + }.addOnFailureListener { + openFallbackScanner(noOfPages) } + } catch (_: Exception) { + openFallbackScanner(noOfPages) + } + } + + private fun openFallbackScanner(noOfPages: Int) { + val intent = createDocumentScanIntent(noOfPages) + try { + ActivityCompat.startActivityForResult( + this.activity, + intent, + START_DOCUMENT_FB_ACTIVITY, + null + ) + } catch (_: ActivityNotFoundException) { + pendingResult?.error("ERROR", "FAILED TO START ACTIVITY", null) } } diff --git a/android/src/main/kotlin/biz/cunning/cunning_document_scanner/fallback/DocumentScannerActivity.kt b/android/src/main/kotlin/biz/cunning/cunning_document_scanner/fallback/DocumentScannerActivity.kt index b50a344..dfd24d1 100644 --- a/android/src/main/kotlin/biz/cunning/cunning_document_scanner/fallback/DocumentScannerActivity.kt +++ b/android/src/main/kotlin/biz/cunning/cunning_document_scanner/fallback/DocumentScannerActivity.kt @@ -93,7 +93,7 @@ class DocumentScannerActivity : AppCompatActivity() { return@CameraUtil } - // get document corners asynchronously using HMS if available, or fallback to default corners + // get default corners for cropping detectCorners(photo) { corners -> document = Document(originalPhotoPath, photo.width, photo.height, corners) @@ -246,41 +246,7 @@ class DocumentScannerActivity : AppCompatActivity() { } } - private fun isHmsAvailable(): Boolean { - return try { - packageManager.getPackageInfo("com.huawei.hwid", 0) - true - } catch (e: PackageManager.NameNotFoundException) { - false - } - } - - private fun isHmsLibraryAvailable(): Boolean { - return try { - Class.forName("com.huawei.hms.mlsdk.dsc.MLDocumentSkewCorrectionAnalyzerFactory") - true - } catch (e: ClassNotFoundException) { - false - } - } - private fun detectCorners(photo: Bitmap, onComplete: (Quad) -> Unit) { - if (isHmsAvailable() && isHmsLibraryAvailable()) { - try { - val detectorClass = Class.forName("biz.cunning.cunning_document_scanner.fallback.HmsEdgeDetector") - val detector = detectorClass.getDeclaredConstructor().newInstance() as EdgeDetector - detector.detect(photo) { detectedQuad -> - if (detectedQuad != null) { - onComplete(detectedQuad) - } else { - onComplete(getDefaultCorners(photo)) - } - } - return - } catch (e: Exception) { - android.util.Log.e("DocumentScannerActivity", "Error loading HmsEdgeDetector dynamically", e) - } - } onComplete(getDefaultCorners(photo)) } diff --git a/android/src/main/kotlin/biz/cunning/cunning_document_scanner/fallback/HmsEdgeDetector.kt b/android/src/main/kotlin/biz/cunning/cunning_document_scanner/fallback/HmsEdgeDetector.kt deleted file mode 100644 index 9645df7..0000000 --- a/android/src/main/kotlin/biz/cunning/cunning_document_scanner/fallback/HmsEdgeDetector.kt +++ /dev/null @@ -1,63 +0,0 @@ -package biz.cunning.cunning_document_scanner.fallback - -import android.graphics.Bitmap -import biz.cunning.cunning_document_scanner.fallback.models.Point -import biz.cunning.cunning_document_scanner.fallback.models.Quad -import com.huawei.hms.mlsdk.common.MLFrame -import com.huawei.hms.mlsdk.dsc.MLDocumentSkewCorrectionAnalyzerFactory -import com.huawei.hms.mlsdk.dsc.MLDocumentSkewCorrectionAnalyzerSetting - -class HmsEdgeDetector : EdgeDetector { - override fun detect(photo: Bitmap, onComplete: (Quad?) -> Unit) { - try { - val setting = MLDocumentSkewCorrectionAnalyzerSetting.Factory().create() - val analyzer = MLDocumentSkewCorrectionAnalyzerFactory.getInstance() - .getDocumentSkewCorrectionAnalyzer(setting) - val frame = MLFrame.fromBitmap(photo) - - analyzer.asyncDocumentSkewDetect(frame) - .addOnSuccessListener { result: com.huawei.hms.mlsdk.dsc.MLDocumentSkewDetectResult? -> - if (result != null) { - val lt = result.leftTopPosition - val rt = result.rightTopPosition - val lb = result.leftBottomPosition - val rb = result.rightBottomPosition - - if (lt != null && rt != null && lb != null && rb != null) { - val tl = Point(lt.x.toDouble(), lt.y.toDouble()) - val tr = Point(rt.x.toDouble(), rt.y.toDouble()) - val bl = Point(lb.x.toDouble(), lb.y.toDouble()) - val br = Point(rb.x.toDouble(), rb.y.toDouble()) - val sortedQuad = sortPoints(listOf(tl, tr, bl, br)) - analyzer.stop() - onComplete(sortedQuad) - return@addOnSuccessListener - } - } - analyzer.stop() - onComplete(null) - } - .addOnFailureListener { - try { - analyzer.stop() - } catch (e: Exception) {} - onComplete(null) - } - } catch (e: Exception) { - onComplete(null) - } - } - - private fun sortPoints(points: List): Quad { - val sortedByY = points.sortedBy { it.y } - val topPoints = sortedByY.take(2).sortedBy { it.x } - val bottomPoints = sortedByY.takeLast(2).sortedBy { it.x } - - val topLeft = topPoints[0] - val topRight = topPoints[1] - val bottomLeft = bottomPoints[0] - val bottomRight = bottomPoints[1] - - return Quad(topLeft, topRight, bottomRight, bottomLeft) - } -} diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 25a96fe..a9db115 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-all.zip diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts index b1033e6..304dd76 100644 --- a/example/android/settings.gradle.kts +++ b/example/android/settings.gradle.kts @@ -1,5 +1,3 @@ -import java.util.Properties - pluginManagement { val flutterSdkPath = java.util.Properties().run { @@ -19,7 +17,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.13.1" apply false + id("com.android.application") version "9.2.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0" } diff --git a/example/pubspec.lock b/example/pubspec.lock index 6fdce19..b77a538 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -71,7 +71,7 @@ packages: path: ".." relative: true source: path - version: "2.5.0" + version: "2.5.1" cupertino_icons: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index eaac160..de99aba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: cunning_document_scanner description: A document scanner plugin for flutter. Scan and crop automatically on iOS and Android. -version: 2.5.1 +version: 2.6.0 homepage: https://cunning.biz repository: https://github.com/jachzen/cunning_document_scanner From 159537731ca2dc92b03e8091b17c41e89782a371 Mon Sep 17 00:00:00 2001 From: Victor Carreras <34163765+vicajilau@users.noreply.github.com> Date: Thu, 9 Jul 2026 18:16:06 +0200 Subject: [PATCH 2/2] chore: remove redundant sourceSets block from build.gradle.kts --- CHANGELOG.md | 1 + android/build.gradle.kts | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a846d98..6314e96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Removed HMS (Huawei Mobile Services) support entirely to ensure 16 KB page-size compatibility on Android 15+ (fixes #146). * Replaced deprecated `getParcelable(key)` with type-safe `androidx.core.os.BundleCompat.getParcelable` for Android 13+ compatibility. * Migrated the legacy `android` block to the modern `configure` block and updated conditional Kotlin plugin application in `build.gradle.kts` to resolve build/deprecation warnings. +* Removed redundant and deprecated `sourceSets` block, as Kotlin source directories are resolved automatically by Gradle. ## 2.5.0 ### General diff --git a/android/build.gradle.kts b/android/build.gradle.kts index bea60df..62b21f2 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -25,12 +25,6 @@ configure { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - - sourceSets { - getByName("main") { - java.srcDir("src/main/kotlin") - } - } } plugins.withId("org.jetbrains.kotlin.android") { extensions.configure {