diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0a5f0549..fa853984 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -6,6 +6,7 @@ on: env: ANDROID_API_LEVEL: "37.0" ANDROID_BUILD_TOOLS: "37.0.0" + GITHUB_TOKEN: ${{ github.token }} JAVA_VERSION: "21" jobs: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 23847060..e0a7e966 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -6,6 +6,7 @@ on: env: ANDROID_API_LEVEL: "37.0" ANDROID_BUILD_TOOLS: "37.0.0" + GITHUB_TOKEN: ${{ github.token }} JAVA_VERSION: "21" jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1acfc167..b5aa88e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,7 @@ on: env: ANDROID_API_LEVEL: "37.0" ANDROID_BUILD_TOOLS: "37.0.0" + GITHUB_TOKEN: ${{ github.token }} JAVA_VERSION: "21" jobs: diff --git a/.gitignore b/.gitignore index 5c915a53..9ccb348b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ .cxx local.properties .idea/ +__pycache__/ +/tmp/ diff --git a/ai/.gitignore b/ai/.gitignore index 42afabfd..a602e597 100644 --- a/ai/.gitignore +++ b/ai/.gitignore @@ -1 +1,4 @@ -/build \ No newline at end of file +/build +/libs/ +/src/main/jniLibs/ +/src/main/assets/models/on-device-llm-openvino/ diff --git a/ai/build.gradle.kts b/ai/build.gradle.kts index 2218887b..7da59f36 100644 --- a/ai/build.gradle.kts +++ b/ai/build.gradle.kts @@ -4,6 +4,38 @@ plugins { alias(libs.plugins.android.library) } +val openvinoGenAiAndroidDir = providers.gradleProperty("openvinoGenAiAndroidDir") +val openvinoAndroidPrebuildRepo = + providers.gradleProperty("openvinoAndroidPrebuildRepo").orElse("embedded-dev-research/openvino-notes") +val openvinoAndroidPrebuildRunId = providers.gradleProperty("openvinoAndroidPrebuildRunId").orElse("25928695317") +val openvinoAndroidPrebuildArtifactName = + providers + .gradleProperty("openvinoAndroidPrebuildArtifactName") + .orElse("openvino-android-arm64-v8a-android-mbind-compat.zip") +val openvinoAndroidPrebuildPackageName = + providers + .gradleProperty("openvinoAndroidPrebuildPackageName") + .orElse("openvino-android-arm64-v8a-android-mbind-compat") +val openvinoAndroidPrebuildDownloadDir = + layout.buildDirectory.dir("openvino/prebuild/download/${openvinoAndroidPrebuildRunId.get()}") +val openvinoAndroidPrebuildExtractDir = + layout.buildDirectory.dir("openvino/prebuild/extracted/${openvinoAndroidPrebuildRunId.get()}") +val openvinoAndroidPrebuildArchive = + openvinoAndroidPrebuildDownloadDir.map { it.file(openvinoAndroidPrebuildArtifactName.get()) } +val openvinoAndroidPrebuildPackageDir = + openvinoAndroidPrebuildExtractDir.map { it.dir(openvinoAndroidPrebuildPackageName.get()) } +val resolvedOpenvinoGenAiAndroidDir = + openvinoGenAiAndroidDir.orElse(openvinoAndroidPrebuildPackageDir.map { it.asFile.absolutePath }) +val openvinoAndroidAbi = "arm64-v8a" +val openvinoRuntimeAssetRootDir = layout.buildDirectory.dir("generated/openvinoRuntimeAssets") +val openvinoRuntimeAssetDir = openvinoRuntimeAssetRootDir.map { it.dir("openvino-runtime") } +val onDeviceLlmModelId = + providers.gradleProperty("onDeviceLlmModelId").orElse("OpenVINO/Qwen3-0.6B-int4-ov") +val onDeviceLlmWeightFormat = providers.gradleProperty("onDeviceLlmWeightFormat").orElse("int4") +val onDeviceLlmPythonVenvDir = layout.buildDirectory.dir("llm/python-venv") +val onDeviceLlmExportDir = layout.buildDirectory.dir("llm/on-device-llm-openvino") +val onDeviceLlmAssetDir = layout.projectDirectory.dir("src/main/assets/models/on-device-llm-openvino") + android { namespace = "com.itlab.ai" compileSdk { @@ -15,6 +47,38 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") + + ndk { + abiFilters += openvinoAndroidAbi + } + + externalNativeBuild { + cmake { + arguments += + listOf( + "-DOPENVINO_GENAI_ANDROID_DIR=${resolvedOpenvinoGenAiAndroidDir.get()}", + "-DANDROID_STL=c++_shared", + ) + } + } + } + + externalNativeBuild { + cmake { + path = file("src/main/cpp/CMakeLists.txt") + } + } + + sourceSets { + getByName("main") { + jniLibs.directories.clear() + jniLibs.directories.add( + file(resolvedOpenvinoGenAiAndroidDir.get()) + .resolve("android-jni") + .absolutePath, + ) + assets.directories.add(openvinoRuntimeAssetRootDir.get().asFile.absolutePath) + } } buildTypes { @@ -26,6 +90,18 @@ android { ) } } + + packaging { + jniLibs { + pickFirsts += "lib/**/libc++_shared.so" + } + } + + lint { + // The OpenVINO GenAI Android prebuild used by this module is arm64-v8a only. + disable += "ChromeOsAbiSupport" + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -42,8 +118,149 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.koin.android) implementation(project(":domain")) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } + +val downloadOpenVinoAndroidPrebuild by tasks.registering(Exec::class) { + group = "ai" + description = "Download the Android OpenVINO prebuild artifact from PR 89." + + onlyIf { !openvinoGenAiAndroidDir.isPresent && !openvinoAndroidPrebuildArchive.get().asFile.isFile } + outputs.file(openvinoAndroidPrebuildArchive) + + doFirst { + openvinoAndroidPrebuildDownloadDir.get().asFile.mkdirs() + } + + commandLine( + "python3", + "scripts/download_openvino_prebuild.py", + "--repo", + openvinoAndroidPrebuildRepo.get(), + "--run-id", + openvinoAndroidPrebuildRunId.get(), + "--artifact-name", + openvinoAndroidPrebuildArtifactName.get(), + "--output", + openvinoAndroidPrebuildArchive.get().asFile.absolutePath, + ) +} + +val extractOpenVinoAndroidPrebuild by tasks.registering(Copy::class) { + group = "ai" + description = "Extract the Android OpenVINO prebuild for native linking and packaging." + + onlyIf { !openvinoGenAiAndroidDir.isPresent } + dependsOn(downloadOpenVinoAndroidPrebuild) + from({ zipTree(openvinoAndroidPrebuildArchive.get().asFile) }) + into(openvinoAndroidPrebuildExtractDir) + outputs.dir(openvinoAndroidPrebuildPackageDir) +} + +val stageOpenVinoRuntimeAssets by tasks.registering { + group = "ai" + description = "Stage OpenVINO runtime metadata that must live next to extracted native libraries." + + dependsOn(extractOpenVinoAndroidPrebuild) + inputs.dir(resolvedOpenvinoGenAiAndroidDir) + outputs.dir(openvinoRuntimeAssetDir) + + doLast { + val packageDir = file(resolvedOpenvinoGenAiAndroidDir.get()) + val jniDir = packageDir.resolve("android-jni/$openvinoAndroidAbi") + val runtimeLibDir = packageDir.resolve("runtime/lib") + val outputRoot = openvinoRuntimeAssetDir.get().asFile + outputRoot.deleteRecursively() + outputRoot.mkdirs() + + val pluginXmlCandidates = + listOf(runtimeLibDir, jniDir) + .flatMap { root -> + if (root.isDirectory) { + val candidates = + root.walkTopDown().filter { candidate -> + candidate.isFile && + candidate.name == "plugins.xml" + } + candidates.toList() + } else { + emptyList() + } + } + val installedPluginXml = pluginXmlCandidates.firstOrNull { it.readText().contains(" + + + + + """.trimIndent() + "\n", + ) + } + } +} + +tasks.register("prepareOpenVinoLlmModel") { + group = "ai" + description = "Export the bundled on-device LLM to an OpenVINO GenAI model bundle." + + inputs.property("modelId", onDeviceLlmModelId) + inputs.property("weightFormat", onDeviceLlmWeightFormat) + outputs.dir(onDeviceLlmExportDir) + + commandLine( + "python3", + "scripts/prepare_openvino_llm_model.py", + "--model-id", + onDeviceLlmModelId.get(), + "--weight-format", + onDeviceLlmWeightFormat.get(), + "--output", + onDeviceLlmExportDir.get().asFile.absolutePath, + "--venv", + onDeviceLlmPythonVenvDir.get().asFile.absolutePath, + "--install-deps", + ) +} + +tasks.register("stageOpenVinoLlmAssets") { + group = "ai" + description = "Copy the prepared OpenVINO LLM model into app assets for local packaging." + dependsOn("prepareOpenVinoLlmModel") + + from(onDeviceLlmExportDir) + into(onDeviceLlmAssetDir) +} + +tasks.named("preBuild") { + dependsOn(extractOpenVinoAndroidPrebuild) + dependsOn(stageOpenVinoRuntimeAssets) + dependsOn("stageOpenVinoLlmAssets") +} diff --git a/ai/gradle.lockfile b/ai/gradle.lockfile index a52ff0a5..80a50a67 100644 --- a/ai/gradle.lockfile +++ b/ai/gradle.lockfile @@ -1,129 +1,206 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.activity:activity:1.8.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.annotation:annotation-experimental:1.4.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.annotation:annotation-jvm:1.8.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.annotation:annotation:1.8.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.activity:activity-ktx:1.12.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.activity:activity:1.12.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.activity:activity:1.8.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.4.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-jvm:1.8.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-jvm:1.9.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.annotation:annotation:1.8.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.9.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath androidx.appcompat:appcompat-resources:1.6.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.appcompat:appcompat-resources:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.appcompat:appcompat-resources:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath androidx.appcompat:appcompat:1.6.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.appcompat:appcompat:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.arch.core:core-runtime:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath -androidx.arch.core:core-runtime:2.2.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.cardview:cardview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.collection:collection-jvm:1.4.2=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.appcompat:appcompat:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.cardview:cardview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection-jvm:1.4.2=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection-ktx:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath +androidx.collection:collection-ktx:1.4.2=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath -androidx.collection:collection:1.4.2=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.concurrent:concurrent-futures-ktx:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -androidx.concurrent:concurrent-futures:1.1.0=debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -androidx.concurrent:concurrent-futures:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -androidx.constraintlayout:constraintlayout-core:1.0.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.collection:collection:1.4.2=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.compose.runtime:runtime-annotation-android:1.9.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.compose.runtime:runtime-annotation:1.9.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.concurrent:concurrent-futures-ktx:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath +androidx.concurrent:concurrent-futures:1.1.0=debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.concurrent:concurrent-futures:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath +androidx.constraintlayout:constraintlayout-core:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath androidx.constraintlayout:constraintlayout-solver:2.0.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.constraintlayout:constraintlayout:2.0.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.constraintlayout:constraintlayout:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -androidx.coordinatorlayout:coordinatorlayout:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.constraintlayout:constraintlayout:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.coordinatorlayout:coordinatorlayout:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.core:core-ktx:1.17.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.core:core-ktx:1.18.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -androidx.core:core-viewtree:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core-ktx:1.18.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.core:core-viewtree:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.core:core:1.17.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.core:core:1.18.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -androidx.cursoradapter:cursoradapter:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.customview:customview:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.18.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.cursoradapter:cursoradapter:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.documentfile:documentfile:1.0.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.drawerlayout:drawerlayout:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.drawerlayout:drawerlayout:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.dynamicanimation:dynamicanimation:1.0.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.dynamicanimation:dynamicanimation:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.dynamicanimation:dynamicanimation:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath androidx.emoji2:emoji2-views-helper:1.2.0=releaseUnitTestRuntimeClasspath -androidx.emoji2:emoji2-views-helper:1.3.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.emoji2:emoji2-views-helper:1.3.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath androidx.emoji2:emoji2:1.2.0=releaseUnitTestRuntimeClasspath -androidx.emoji2:emoji2:1.3.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.emoji2:emoji2:1.3.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.fragment:fragment-ktx:1.8.9=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath androidx.fragment:fragment:1.3.6=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.fragment:fragment:1.5.4=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.fragment:fragment:1.8.9=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath androidx.graphics:graphics-shapes-android:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath -androidx.graphics:graphics-shapes-desktop:1.0.1=debugLintChecksClasspath,releaseLintChecksClasspath -androidx.graphics:graphics-shapes:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -androidx.interpolator:interpolator:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.graphics:graphics-shapes-desktop:1.0.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugUnitTestLintChecksClasspath,releaseLintChecksClasspath +androidx.graphics:graphics-shapes:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.interpolator:interpolator:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.legacy:legacy-support-core-utils:1.0.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.lifecycle:lifecycle-common:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.lifecycle:lifecycle-livedata-core:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.lifecycle:lifecycle-livedata:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.lifecycle:lifecycle-process:2.6.2=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -androidx.lifecycle:lifecycle-runtime:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.lifecycle:lifecycle-viewmodel:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-jvm:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.6.2=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core-ktx:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.6.2=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.6.2=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-process:2.10.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-process:2.6.2=releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime-android:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-runtime-ktx-android:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-runtime-ktx:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.6.2=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel-android:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel-savedstate:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.6.2=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.localbroadcastmanager:localbroadcastmanager:1.0.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.navigationevent:navigationevent-android:1.0.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.navigationevent:navigationevent:1.0.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath androidx.print:print:1.0.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.profileinstaller:profileinstaller:1.3.0=releaseUnitTestRuntimeClasspath -androidx.profileinstaller:profileinstaller:1.3.1=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.profileinstaller:profileinstaller:1.4.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath androidx.recyclerview:recyclerview:1.1.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.recyclerview:recyclerview:1.2.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -androidx.resourceinspection:resourceinspection-annotation:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.savedstate:savedstate:1.2.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.startup:startup-runtime:1.1.1=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -androidx.test.espresso:espresso-core:3.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -androidx.test.espresso:espresso-idling-resource:3.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -androidx.test.ext:junit:1.3.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -androidx.test.services:storage:1.6.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -androidx.test:core:1.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -androidx.test:monitor:1.8.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -androidx.test:runner:1.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath +androidx.recyclerview:recyclerview:1.2.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.resourceinspection:resourceinspection-annotation:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate-android:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.savedstate:savedstate-ktx:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.savedstate:savedstate:1.2.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.startup:startup-runtime:1.1.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-core:3.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath +androidx.test.espresso:espresso-idling-resource:3.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath +androidx.test.ext:junit:1.3.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath +androidx.test.services:storage:1.6.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath +androidx.test:core:1.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath +androidx.test:monitor:1.8.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath +androidx.test:runner:1.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath androidx.tracing:tracing:1.1.0=debugAndroidTestCompileClasspath -androidx.tracing:tracing:1.2.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.2.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath androidx.transition:transition:1.2.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.transition:transition:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -androidx.vectordrawable:vectordrawable-animated:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.vectordrawable:vectordrawable:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.viewpager2:viewpager2:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.transition:transition:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +androidx.vectordrawable:vectordrawable-animated:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.vectordrawable:vectordrawable:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager2:viewpager2:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath ch.qos.logback:logback-classic:1.3.14=ktlint ch.qos.logback:logback-core:1.3.14=ktlint -com.android.tools.analytics-library:protos:32.2.0=androidLintTool -com.android.tools.analytics-library:shared:32.2.0=androidLintTool +co.touchlab:stately-concurrency-jvm:2.1.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +co.touchlab:stately-concurrency:2.1.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +co.touchlab:stately-concurrent-collections-jvm:2.1.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +co.touchlab:stately-concurrent-collections:2.1.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +co.touchlab:stately-strict-jvm:2.1.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +co.touchlab:stately-strict:2.1.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +com.android.tools.analytics-library:protos:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.analytics-library:shared:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle com.android.tools.analytics-library:tracker:32.2.0=androidLintTool -com.android.tools.build:aapt2-proto:9.2.0-15009934=androidLintTool +com.android.tools.build:aapt2-proto:9.2.0-15009934=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle com.android.tools.build:builder-model:9.2.0=androidLintTool com.android.tools.build:manifest-merger:32.2.0=androidLintTool -com.android.tools.ddms:ddmlib:32.2.0=androidLintTool +com.android.tools.ddms:ddmlib:32.2.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +com.android.tools.emulator:proto:32.2.0=unified-test-platform-android-test-plugin-host-emulator-control com.android.tools.external.com-intellij:intellij-core:32.2.0=androidLintTool com.android.tools.external.com-intellij:kotlin-compiler:32.2.0=androidLintTool com.android.tools.external.org-jetbrains:uast:32.2.0=androidLintTool -com.android.tools.layoutlib:layoutlib-api:32.2.0=androidLintTool +com.android.tools.layoutlib:layoutlib-api:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle com.android.tools.lint:lint-api:32.2.0=androidLintTool com.android.tools.lint:lint-checks:32.2.0=androidLintTool com.android.tools.lint:lint-gradle:32.2.0=androidLintTool com.android.tools.lint:lint-model:32.2.0=androidLintTool com.android.tools.lint:lint-typedef-remover:32.2.0=androidLintTool com.android.tools.lint:lint:32.2.0=androidLintTool -com.android.tools:annotations:32.2.0=androidLintTool -com.android.tools:common:32.2.0=androidLintTool -com.android.tools:dvlib:32.2.0=androidLintTool +com.android.tools.utp:android-device-provider-ddmlib-proto:32.2.0=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-gradle-work-action +com.android.tools.utp:android-device-provider-ddmlib:32.2.0=unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:32.2.0=unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-gradle-work-action +com.android.tools.utp:android-test-plugin-host-additional-test-output:32.2.0=unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-apk-installer-proto:32.2.0=unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-gradle-work-action +com.android.tools.utp:android-test-plugin-host-apk-installer:32.2.0=unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-coverage-proto:32.2.0=unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-gradle-work-action +com.android.tools.utp:android-test-plugin-host-coverage:32.2.0=unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-device-info-proto:32.2.0=unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-device-info:32.2.0=unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-emulator-control-proto:32.2.0=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action +com.android.tools.utp:android-test-plugin-host-emulator-control:32.2.0=unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-logcat-proto:32.2.0=unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-gradle-work-action +com.android.tools.utp:android-test-plugin-host-logcat:32.2.0=unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:32.2.0=unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +com.android.tools.utp:android-test-plugin-result-listener-gradle:32.2.0=unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:gradle-work-action:32.2.0=unified-test-platform-gradle-work-action +com.android.tools.utp:utp-common:32.2.0=unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-logcat +com.android.tools:annotations:32.2.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +com.android.tools:common:32.2.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +com.android.tools:dvlib:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle com.android.tools:play-sdk-proto:32.2.0=androidLintTool -com.android.tools:repository:32.2.0=androidLintTool -com.android.tools:sdk-common:32.2.0=androidLintTool -com.android.tools:sdklib:32.2.0=androidLintTool +com.android.tools:repository:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools:sdk-common:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools:sdklib:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle com.github.ajalt.clikt:clikt-jvm:5.0.2=ktlint com.github.ajalt.clikt:clikt:5.0.2=ktlint com.google.android.material:material:1.10.0=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -com.google.android.material:material:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -com.google.code.findbugs:jsr305:3.0.2=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -com.google.code.gson:gson:2.11.0=androidLintTool -com.google.errorprone:error_prone_annotations:2.15.0=debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -com.google.errorprone:error_prone_annotations:2.28.0=androidLintTool -com.google.errorprone:error_prone_annotations:2.30.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -com.google.guava:failureaccess:1.0.2=androidLintTool -com.google.guava:guava:33.3.1-jre=androidLintTool -com.google.guava:listenablefuture:1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=androidLintTool -com.google.j2objc:j2objc-annotations:3.0.0=androidLintTool -com.google.jimfs:jimfs:1.1=androidLintTool +com.google.android.material:material:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +com.google.android:annotations:4.1.1.4=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core +com.google.api.grpc:proto-google-common-protos:2.17.0=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core +com.google.api.grpc:proto-google-common-protos:2.48.0=unified-test-platform-android-test-plugin-host-emulator-control +com.google.auto.service:auto-service-annotations:1.1.1=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle +com.google.auto.service:auto-service:1.1.1=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle +com.google.auto:auto-common:1.2.1=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.findbugs:jsr305:3.0.2=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher +com.google.code.gson:gson:2.10.1=unified-test-platform-core,unified-test-platform-gradle-work-action +com.google.code.gson:gson:2.11.0=androidLintTool,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.gson:gson:2.8.9=unified-test-platform-android-driver-instrumentation,unified-test-platform-launcher +com.google.crypto.tink:tink:1.18.0=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action +com.google.dagger:dagger:2.48=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action +com.google.errorprone:error_prone_annotations:2.15.0=debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.23.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher +com.google.errorprone:error_prone_annotations:2.28.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +com.google.errorprone:error_prone_annotations:2.30.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,unified-test-platform-android-test-plugin-host-emulator-control +com.google.guava:failureaccess:1.0.1=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher +com.google.guava:failureaccess:1.0.2=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +com.google.guava:guava:32.0.1-jre=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher +com.google.guava:guava:33.3.1-jre=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +com.google.guava:listenablefuture:1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher +com.google.j2objc:j2objc-annotations:2.8=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher +com.google.j2objc:j2objc-annotations:3.0.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +com.google.jimfs:jimfs:1.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +com.google.protobuf:protobuf-java-util:3.22.3=unified-test-platform-core +com.google.protobuf:protobuf-java-util:4.28.3=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher com.google.protobuf:protobuf-java:3.25.5=androidLintTool -com.pinterest.ktlint:ktlint-cli-reporter-baseline:1.5.0=ktlint +com.google.protobuf:protobuf-java:4.28.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher +com.google.protobuf:protobuf-kotlin:4.28.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher +com.google.testing.platform:android-device-provider-local:0.0.9-alpha04=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:android-driver-instrumentation:0.0.9-alpha04=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control +com.google.testing.platform:android-test-plugin:0.0.9-alpha04=unified-test-platform-android-test-plugin +com.google.testing.platform:core-proto:0.0.9-alpha04=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +com.google.testing.platform:core:0.0.9-alpha04=unified-test-platform-core +com.google.testing.platform:launcher:0.0.9-alpha04=unified-test-platform-gradle-work-action,unified-test-platform-launcher +com.pinterest.ktlint:ktlint-cli-reporter-baseline:1.5.0=ktlint,ktlintBaselineReporter com.pinterest.ktlint:ktlint-cli-reporter-checkstyle:1.5.0=ktlint -com.pinterest.ktlint:ktlint-cli-reporter-core:1.5.0=ktlint +com.pinterest.ktlint:ktlint-cli-reporter-core:1.5.0=ktlint,ktlintBaselineReporter com.pinterest.ktlint:ktlint-cli-reporter-format:1.5.0=ktlint com.pinterest.ktlint:ktlint-cli-reporter-html:1.5.0=ktlint com.pinterest.ktlint:ktlint-cli-reporter-json:1.5.0=ktlint @@ -132,24 +209,24 @@ com.pinterest.ktlint:ktlint-cli-reporter-plain:1.5.0=ktlint com.pinterest.ktlint:ktlint-cli-reporter-sarif:1.5.0=ktlint com.pinterest.ktlint:ktlint-cli-ruleset-core:1.5.0=ktlint,ktlintRuleset com.pinterest.ktlint:ktlint-cli:1.5.0=ktlint -com.pinterest.ktlint:ktlint-logger:1.5.0=ktlint,ktlintRuleset -com.pinterest.ktlint:ktlint-rule-engine-core:1.5.0=ktlint,ktlintRuleset +com.pinterest.ktlint:ktlint-logger:1.5.0=ktlint,ktlintBaselineReporter,ktlintRuleset +com.pinterest.ktlint:ktlint-rule-engine-core:1.5.0=ktlint,ktlintBaselineReporter,ktlintRuleset com.pinterest.ktlint:ktlint-rule-engine:1.5.0=ktlint com.pinterest.ktlint:ktlint-ruleset-standard:1.5.0=ktlint,ktlintRuleset -com.sun.istack:istack-commons-runtime:3.0.8=androidLintTool -com.sun.xml.fastinfoset:FastInfoset:1.2.16=androidLintTool -commons-codec:commons-codec:1.17.1=androidLintTool -commons-io:commons-io:2.16.1=androidLintTool -commons-logging:commons-logging:1.2=androidLintTool +com.sun.istack:istack-commons-runtime:3.0.8=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +com.sun.xml.fastinfoset:FastInfoset:1.2.16=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +commons-codec:commons-codec:1.17.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +commons-io:commons-io:2.16.1=androidLintTool,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +commons-logging:commons-logging:1.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle dev.drewhamilton.poko:poko-annotations-jvm:0.17.1=detekt -dev.drewhamilton.poko:poko-annotations-jvm:0.18.0=ktlint,ktlintRuleset +dev.drewhamilton.poko:poko-annotations-jvm:0.18.0=ktlint,ktlintBaselineReporter,ktlintRuleset dev.drewhamilton.poko:poko-annotations:0.17.1=detekt -dev.drewhamilton.poko:poko-annotations:0.18.0=ktlint,ktlintRuleset +dev.drewhamilton.poko:poko-annotations:0.18.0=ktlint,ktlintBaselineReporter,ktlintRuleset io.github.davidburstrom.contester:contester-breakpoint:0.2.0=detekt io.github.detekt.sarif4k:sarif4k-jvm:0.6.0=detekt,ktlint,ktlintReporter io.github.detekt.sarif4k:sarif4k:0.6.0=detekt,ktlint,ktlintReporter -io.github.oshai:kotlin-logging-jvm:7.0.3=ktlint,ktlintReporter,ktlintRuleset -io.github.oshai:kotlin-logging:5.1.0=ktlint,ktlintReporter +io.github.oshai:kotlin-logging-jvm:7.0.3=ktlint,ktlintBaselineReporter,ktlintReporter,ktlintRuleset +io.github.oshai:kotlin-logging:5.1.0=ktlint,ktlintBaselineReporter,ktlintReporter io.gitlab.arturbosch.detekt:detekt-api:1.23.8=detekt io.gitlab.arturbosch.detekt:detekt-cli:1.23.8=detekt io.gitlab.arturbosch.detekt:detekt-core:1.23.8=detekt @@ -173,94 +250,168 @@ io.gitlab.arturbosch.detekt:detekt-rules-style:1.23.8=detekt io.gitlab.arturbosch.detekt:detekt-rules:1.23.8=detekt io.gitlab.arturbosch.detekt:detekt-tooling:1.23.8=detekt io.gitlab.arturbosch.detekt:detekt-utils:1.23.8=detekt -jakarta.activation:jakarta.activation-api:1.2.1=androidLintTool -jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=androidLintTool -javax.inject:javax.inject:1=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath -junit:junit:4.13.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -net.java.dev.jna:jna-platform:5.6.0=androidLintTool -net.java.dev.jna:jna:5.6.0=androidLintTool -net.sf.kxml:kxml2:2.3.0=androidLintTool -org.apache.commons:commons-compress:1.27.1=androidLintTool -org.apache.commons:commons-lang3:3.16.0=androidLintTool -org.apache.httpcomponents:httpclient:4.5.6=androidLintTool -org.apache.httpcomponents:httpcore:4.4.16=androidLintTool -org.apache.httpcomponents:httpmime:4.5.6=androidLintTool -org.bouncycastle:bcpkix-jdk18on:1.79=androidLintTool -org.bouncycastle:bcprov-jdk18on:1.79=androidLintTool -org.bouncycastle:bcutil-jdk18on:1.79=androidLintTool -org.checkerframework:checker-qual:3.43.0=androidLintTool +io.grpc:grpc-api:1.57.2=unified-test-platform-core +io.grpc:grpc-api:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control +io.grpc:grpc-context:1.57.2=unified-test-platform-core +io.grpc:grpc-context:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control +io.grpc:grpc-core:1.57.2=unified-test-platform-core +io.grpc:grpc-core:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control +io.grpc:grpc-netty:1.57.2=unified-test-platform-core +io.grpc:grpc-netty:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control +io.grpc:grpc-protobuf-lite:1.57.2=unified-test-platform-core +io.grpc:grpc-protobuf-lite:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control +io.grpc:grpc-protobuf:1.57.2=unified-test-platform-core +io.grpc:grpc-protobuf:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control +io.grpc:grpc-services:1.57.2=unified-test-platform-core +io.grpc:grpc-stub:1.57.2=unified-test-platform-core +io.grpc:grpc-stub:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control +io.grpc:grpc-util:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control +io.insert-koin:koin-android:4.2.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +io.insert-koin:koin-core-jvm:4.2.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +io.insert-koin:koin-core-viewmodel-android:4.2.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +io.insert-koin:koin-core-viewmodel:4.2.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +io.insert-koin:koin-core:4.2.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +io.netty:netty-buffer:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-buffer:4.1.93.Final=unified-test-platform-core +io.netty:netty-codec-http2:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-codec-http2:4.1.93.Final=unified-test-platform-core +io.netty:netty-codec-http:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-codec-http:4.1.93.Final=unified-test-platform-core +io.netty:netty-codec-socks:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-codec-socks:4.1.93.Final=unified-test-platform-core +io.netty:netty-codec:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-codec:4.1.93.Final=unified-test-platform-core +io.netty:netty-common:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-common:4.1.93.Final=unified-test-platform-core +io.netty:netty-handler-proxy:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-handler-proxy:4.1.93.Final=unified-test-platform-core +io.netty:netty-handler:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-handler:4.1.93.Final=unified-test-platform-core +io.netty:netty-resolver:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-resolver:4.1.93.Final=unified-test-platform-core +io.netty:netty-transport-native-unix-common:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-transport-native-unix-common:4.1.93.Final=unified-test-platform-core +io.netty:netty-transport:4.1.110.Final=unified-test-platform-android-test-plugin-host-emulator-control +io.netty:netty-transport:4.1.93.Final=unified-test-platform-core +io.opencensus:opencensus-api:0.31.0=unified-test-platform-core +io.opencensus:opencensus-proto:0.2.0=unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher +io.perfmark:perfmark-api:0.26.0=unified-test-platform-core +io.perfmark:perfmark-api:0.27.0=unified-test-platform-android-test-plugin-host-emulator-control +jakarta.activation:jakarta.activation-api:1.2.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +javax.annotation:javax.annotation-api:1.3.2=unified-test-platform-android-test-plugin-host-emulator-control +javax.inject:javax.inject:1=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action +junit:junit:4.13.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.java.dev.jna:jna-platform:5.6.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +net.java.dev.jna:jna:5.6.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +net.sf.kxml:kxml2:2.3.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +org.apache.commons:commons-compress:1.27.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.apache.commons:commons-lang3:3.16.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.apache.httpcomponents:httpclient:4.5.6=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.apache.httpcomponents:httpcore:4.4.16=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.apache.httpcomponents:httpmime:4.5.6=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.bouncycastle:bcpkix-jdk18on:1.79=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.bouncycastle:bcprov-jdk18on:1.79=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.bouncycastle:bcutil-jdk18on:1.79=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.checkerframework:checker-qual:3.33.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher +org.checkerframework:checker-qual:3.43.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action org.codehaus.groovy:groovy:3.0.22=androidLintTool -org.ec4j.core:ec4j-core:1.1.0=ktlint,ktlintRuleset -org.glassfish.jaxb:jaxb-runtime:2.3.2=androidLintTool -org.glassfish.jaxb:txw2:2.3.2=androidLintTool -org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.23=unified-test-platform-core +org.codehaus.mojo:animal-sniffer-annotations:1.24=unified-test-platform-android-test-plugin-host-emulator-control +org.ec4j.core:ec4j-core:1.1.0=ktlint,ktlintBaselineReporter,ktlintRuleset +org.glassfish.jaxb:jaxb-runtime:2.3.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.glassfish.jaxb:txw2:2.3.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.14=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.14=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.14=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.14=androidJacocoAnt org.jcommander:jcommander:1.85=detekt -org.jetbrains.intellij.deps:trove4j:1.0.20200330=detekt,kotlinCompilerClasspath,ktlint,ktlintRuleset -org.jetbrains.kotlin:kotlin-bom:1.8.22=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -org.jetbrains.kotlin:kotlin-build-tools-api:2.3.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-build-tools-compat:2.3.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-build-tools-cri-impl:2.3.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-build-tools-impl:2.3.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-compiler-embeddable:2.0.21=detekt,kotlinCompilerClasspath -org.jetbrains.kotlin:kotlin-compiler-embeddable:2.1.0=ktlint,ktlintRuleset -org.jetbrains.kotlin:kotlin-compiler-embeddable:2.3.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-compiler-runner:2.3.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-daemon-client:2.3.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-daemon-embeddable:2.0.21=detekt,kotlinCompilerClasspath -org.jetbrains.kotlin:kotlin-daemon-embeddable:2.1.0=ktlint,ktlintRuleset -org.jetbrains.kotlin:kotlin-daemon-embeddable:2.3.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,ktlint,ktlintRuleset +org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.6=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.9.6=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.androidx.savedstate:savedstate:1.3.6=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=detekt,ktlint,ktlintBaselineReporter,ktlintRuleset +org.jetbrains.kotlin:kotlin-bom:1.8.22=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-build-tools-api:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-build-tools-compat:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-build-tools-cri-impl:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-build-tools-impl:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:2.0.21=detekt +org.jetbrains.kotlin:kotlin-compiler-embeddable:2.1.0=ktlint,ktlintBaselineReporter,ktlintRuleset +org.jetbrains.kotlin:kotlin-compiler-embeddable:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-compiler-runner:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-daemon-client:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:2.0.21=detekt +org.jetbrains.kotlin:kotlin-daemon-embeddable:2.1.0=ktlint,ktlintBaselineReporter,ktlintRuleset +org.jetbrains.kotlin:kotlin-daemon-embeddable:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,ktlint,ktlintBaselineReporter,ktlintRuleset +org.jetbrains.kotlin:kotlin-reflect:1.8.21=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher org.jetbrains.kotlin:kotlin-reflect:2.0.21=detekt -org.jetbrains.kotlin:kotlin-reflect:2.2.10=androidLintTool -org.jetbrains.kotlin:kotlin-script-runtime:2.0.21=detekt,kotlinCompilerClasspath -org.jetbrains.kotlin:kotlin-script-runtime:2.1.0=ktlint,ktlintRuleset -org.jetbrains.kotlin:kotlin-script-runtime:2.3.21=kotlinBuildToolsApiClasspath +org.jetbrains.kotlin:kotlin-reflect:2.2.10=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-script-runtime:2.0.21=detekt +org.jetbrains.kotlin:kotlin-script-runtime:2.1.0=ktlint,ktlintBaselineReporter,ktlintRuleset +org.jetbrains.kotlin:kotlin-script-runtime:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21=unified-test-platform-android-test-plugin,unified-test-platform-core +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-launcher org.jetbrains.kotlin:kotlin-stdlib-common:2.0.21=detekt org.jetbrains.kotlin:kotlin-stdlib-common:2.1.0=ktlintReporter +org.jetbrains.kotlin:kotlin-stdlib-common:2.2.10=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-gradle-work-action org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0=detekt,ktlintReporter -org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.10=androidLintTool +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher +org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.10=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0=detekt,ktlintReporter -org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.10=androidLintTool -org.jetbrains.kotlin:kotlin-stdlib:2.0.21=detekt,kotlinCompilerClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib:2.1.0=ktlint,ktlintReporter,ktlintRuleset -org.jetbrains.kotlin:kotlin-stdlib:2.2.10=androidLintTool -org.jetbrains.kotlin:kotlin-stdlib:2.3.21=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinBuildToolsApiClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -org.jetbrains.kotlin:kotlin-tooling-core:2.3.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher +org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.10=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +org.jetbrains.kotlin:kotlin-stdlib:1.8.21=unified-test-platform-android-test-plugin,unified-test-platform-core +org.jetbrains.kotlin:kotlin-stdlib:1.9.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-launcher +org.jetbrains.kotlin:kotlin-stdlib:2.0.21=detekt,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:2.1.0=ktlint,ktlintBaselineReporter,ktlintReporter,ktlintRuleset +org.jetbrains.kotlin:kotlin-stdlib:2.2.10=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action +org.jetbrains.kotlin:kotlin-stdlib:2.3.21=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlin:kotlin-tooling-core:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath +org.jetbrains.kotlinx:atomicfu-jvm:0.22.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher +org.jetbrains.kotlinx:atomicfu:0.22.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0=androidLintTool -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4=detekt,kotlinCompilerClasspath,ktlint,ktlintRuleset -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4=detekt,ktlint,ktlintBaselineReporter,ktlintRuleset +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0=androidLintTool -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1=releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0=androidLintTool -org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.8.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-datetime:0.8.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.8.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-datetime:0.8.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath org.jetbrains.kotlinx:kotlinx-html-jvm:0.8.1=detekt -org.jetbrains.kotlinx:kotlinx-serialization-bom:1.11.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.11.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-serialization-bom:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath +org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.4.1=detekt,ktlintReporter -org.jetbrains.kotlinx:kotlinx-serialization-core:1.11.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath +org.jetbrains.kotlinx:kotlinx-serialization-core:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath org.jetbrains.kotlinx:kotlinx-serialization-core:1.4.1=detekt,ktlintReporter -org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.11.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath +org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.4.1=detekt,ktlintReporter -org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0=debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1=detekt,ktlintReporter org.jetbrains.kotlinx:kover-jvm-agent:0.9.8=koverJvmAgent,koverJvmReporter -org.jetbrains:annotations:13.0=detekt,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,ktlint,ktlintReporter,ktlintRuleset -org.jetbrains:annotations:23.0.0=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.jspecify:jspecify:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.jvnet.staxex:stax-ex:1.8.1=androidLintTool +org.jetbrains:annotations:13.0=detekt,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,ktlint,ktlintBaselineReporter,ktlintReporter,ktlintRuleset +org.jetbrains:annotations:23.0.0=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher +org.jspecify:jspecify:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jvnet.staxex:stax-ex:1.8.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle org.ow2.asm:asm-analysis:9.9=androidLintTool -org.ow2.asm:asm-commons:9.9=androidLintTool -org.ow2.asm:asm-tree:9.9=androidLintTool -org.ow2.asm:asm:9.9=androidLintTool +org.ow2.asm:asm-commons:9.9=androidJacocoAnt,androidLintTool +org.ow2.asm:asm-tree:9.9=androidJacocoAnt,androidLintTool +org.ow2.asm:asm:9.9=androidJacocoAnt,androidLintTool org.slf4j:slf4j-api:2.0.7=ktlint org.snakeyaml:snakeyaml-engine:2.7=detekt -empty=androidApis,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,detektPlugins,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,koverExternalArtifacts,lintChecks,lintPublish,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath +empty=androidApis,androidJdkImage,androidTestUtil,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAnnotationProcessorClasspath,debugImplementationDependenciesMetadata,debugUnitTestAnnotationProcessorClasspath,debugUnitTestImplementationDependenciesMetadata,detektPlugins,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,koverExternalArtifacts,koverExternalArtifactsDebug,koverExternalArtifactsRelease,lintChecks,lintPublish,releaseAnnotationProcessorClasspath,releaseImplementationDependenciesMetadata,releaseUnitTestAnnotationProcessorClasspath diff --git a/ai/scripts/download_openvino_prebuild.py b/ai/scripts/download_openvino_prebuild.py new file mode 100644 index 00000000..f990168b --- /dev/null +++ b/ai/scripts/download_openvino_prebuild.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import http.client +import json +import os +import subprocess +import sys +import time +import urllib.error +import urllib.request +import zipfile +from pathlib import Path + +CHUNK_SIZE = 8 * 1024 * 1024 +PROGRESS_STEP = 64 * 1024 * 1024 + + +def github_token() -> str: + token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN") + if token: + return token + + try: + return subprocess.check_output(["gh", "auth", "token"], text=True).strip() + except (FileNotFoundError, subprocess.CalledProcessError) as exc: + raise SystemExit("Set GITHUB_TOKEN/GH_TOKEN or authenticate GitHub CLI with `gh auth login`.") from exc + + +def request(url: str, token: str, timeout: int) -> urllib.request.Request: + return urllib.request.Request( + url, + headers={ + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {token}", + "X-GitHub-Api-Version": "2022-11-28", + "User-Agent": "openvino-notes-prebuild-downloader", + }, + ) + + +def download_request(url: str, range_start: int = 0) -> urllib.request.Request: + headers = { + "User-Agent": "openvino-notes-prebuild-downloader", + } + if range_start > 0: + headers["Range"] = f"bytes={range_start}-" + + return urllib.request.Request( + url, + headers=headers, + ) + + +class NoRedirectHandler(urllib.request.HTTPRedirectHandler): + def redirect_request(self, req, fp, code, msg, headers, newurl): # noqa: ANN001 + return None + + +def load_json(url: str, token: str, timeout: int) -> dict: + with urllib.request.urlopen(request(url, token, timeout), timeout=timeout) as response: + return json.loads(response.read().decode("utf-8")) + + +def find_artifact_download_url(repo: str, run_id: str, artifact_name: str, token: str, timeout: int) -> str: + data = load_json(f"https://api.github.com/repos/{repo}/actions/runs/{run_id}/artifacts", token, timeout) + for artifact in data.get("artifacts", []): + if artifact.get("name") == artifact_name and not artifact.get("expired", False): + return artifact["archive_download_url"] + + available = ", ".join(artifact.get("name", "") for artifact in data.get("artifacts", [])) + raise SystemExit(f"Artifact '{artifact_name}' was not found in run {run_id}. Available artifacts: {available}") + + +def resolve_artifact_archive_url(url: str, token: str, timeout: int) -> str: + opener = urllib.request.build_opener(NoRedirectHandler) + + try: + with opener.open(request(url, token, timeout), timeout=timeout) as response: + return response.url + except urllib.error.HTTPError as exc: + if exc.code in (301, 302, 303, 307, 308): + location = exc.headers.get("Location") + if location: + return location + raise + + +def stream_download(url: str, temp_path: Path, timeout: int) -> None: + resume_from = temp_path.stat().st_size if temp_path.exists() else 0 + + with urllib.request.urlopen(download_request(url, resume_from), timeout=timeout) as response: + status = response.status + append = resume_from > 0 and status == 206 + if resume_from > 0 and not append: + print("Server did not accept byte-range resume; restarting download.", flush=True) + resume_from = 0 + + mode = "ab" if append else "wb" + downloaded = resume_from + next_report = ((downloaded // PROGRESS_STEP) + 1) * PROGRESS_STEP + + with temp_path.open(mode) as output: + while True: + chunk = response.read(CHUNK_SIZE) + if not chunk: + break + + output.write(chunk) + downloaded += len(chunk) + + if downloaded >= next_report: + print(f"Downloaded {downloaded // (1024 * 1024)} MiB...", flush=True) + next_report += PROGRESS_STEP + + +def download_with_retries(url: str, destination: Path, token: str, timeout: int, retries: int) -> None: + destination.parent.mkdir(parents=True, exist_ok=True) + temp_path = destination.with_suffix(destination.suffix + ".tmp") + + for attempt in range(1, retries + 1): + try: + print(f"Downloading GitHub artifact archive, attempt {attempt}/{retries}") + archive_url = resolve_artifact_archive_url(url, token, timeout) + stream_download(archive_url, temp_path, timeout) + temp_path.replace(destination) + return + except urllib.error.HTTPError as exc: + if exc.code == 416 and temp_path.exists() and temp_path.stat().st_size > 0: + temp_path.replace(destination) + return + if attempt == retries: + raise + delay_seconds = min(30, attempt * 5) + print(f"Download failed: {exc}. Retrying in {delay_seconds}s.", file=sys.stderr) + time.sleep(delay_seconds) + except (http.client.IncompleteRead, urllib.error.URLError, TimeoutError, OSError) as exc: + if attempt == retries: + raise + delay_seconds = min(30, attempt * 5) + print(f"Download failed: {exc}. Retrying in {delay_seconds}s.", file=sys.stderr) + time.sleep(delay_seconds) + + +def extract_inner_zip(artifact_archive: Path, inner_name: str, output: Path) -> None: + output.parent.mkdir(parents=True, exist_ok=True) + temp_output = output.with_suffix(output.suffix + ".tmp") + + if temp_output.exists(): + temp_output.unlink() + + with zipfile.ZipFile(artifact_archive) as archive: + names = archive.namelist() + member_name = inner_name if inner_name in names else None + if member_name is None and len(names) == 1: + member_name = names[0] + if member_name is None: + raise SystemExit(f"Artifact archive does not contain '{inner_name}'. Members: {', '.join(names)}") + + with archive.open(member_name) as source, temp_output.open("wb") as target: + while True: + chunk = source.read(CHUNK_SIZE) + if not chunk: + break + target.write(chunk) + + temp_output.replace(output) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--repo", required=True) + parser.add_argument("--run-id", required=True) + parser.add_argument("--artifact-name", required=True) + parser.add_argument("--output", required=True, type=Path) + parser.add_argument("--timeout", type=int, default=60) + parser.add_argument("--retries", type=int, default=20) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + if args.output.is_file() and args.output.stat().st_size > 0: + print(f"Reusing existing prebuild archive: {args.output}") + return + + token = github_token() + download_url = find_artifact_download_url(args.repo, args.run_id, args.artifact_name, token, args.timeout) + artifact_archive = args.output.with_suffix(args.output.suffix + ".github-artifact.zip") + + download_with_retries(download_url, artifact_archive, token, args.timeout, args.retries) + extract_inner_zip(artifact_archive, args.artifact_name, args.output) + print(f"Downloaded prebuild archive: {args.output}") + + +if __name__ == "__main__": + main() diff --git a/ai/scripts/prepare_openvino_llm_model.py b/ai/scripts/prepare_openvino_llm_model.py new file mode 100644 index 00000000..c71aae95 --- /dev/null +++ b/ai/scripts/prepare_openvino_llm_model.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +"""Export a Hugging Face causal LLM to an OpenVINO GenAI-ready bundle.""" + +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import sys +import urllib.parse +import urllib.request +import venv +from pathlib import Path + + +DEFAULT_MODEL_ID = "OpenVINO/Qwen3-0.6B-int4-ov" +REQUIRED_EXPORT_FILES = ( + "openvino_model.xml", + "openvino_model.bin", +) +OPENVINO_BUNDLE_PATTERNS = ( + "*.json", + "*.txt", + "*.xml", + "*.bin", + "merges.txt", + "vocab.json", + "tokenizer.model", +) + + +def run(command: list[str]) -> None: + subprocess.check_call(command) + + +def executable_in_venv(venv_dir: Path, executable: str) -> Path: + bin_dir = "Scripts" if os.name == "nt" else "bin" + suffix = ".exe" if os.name == "nt" else "" + return venv_dir / bin_dir / f"{executable}{suffix}" + + +def ensure_venv(venv_dir: Path) -> tuple[Path, Path]: + python = executable_in_venv(venv_dir, "python") + optimum_cli = executable_in_venv(venv_dir, "optimum-cli") + if not python.exists(): + venv.EnvBuilder(with_pip=True).create(venv_dir) + return python, optimum_cli + + +def export_is_complete(output: Path, model_id: str, weight_format: str) -> bool: + marker = output / ".openvino_llm_export_complete" + if not marker.exists() or not all((output / name).exists() for name in REQUIRED_EXPORT_FILES): + return False + + marker_values = dict( + line.split("=", 1) + for line in marker.read_text(encoding="utf-8").splitlines() + if "=" in line + ) + return marker_values.get("model_id") == model_id and marker_values.get("weight_format") == weight_format + + +def repo_has_openvino_bundle(model_id: str) -> bool: + try: + url = f"https://huggingface.co/api/models/{urllib.parse.quote(model_id, safe='/')}?blobs=true" + with urllib.request.urlopen(url, timeout=45) as response: + model_info = json.load(response) + files = {sibling.get("rfilename") for sibling in model_info.get("siblings", [])} + except Exception: + return False + return all(name in files for name in REQUIRED_EXPORT_FILES) + + +def download_openvino_bundle(python: Path, model_id: str, output: Path) -> None: + script = """ +import shutil +import sys +from pathlib import Path + +from huggingface_hub import snapshot_download + +model_id = sys.argv[1] +output = Path(sys.argv[2]) +patterns = sys.argv[3].split("\\n") + +snapshot_dir = Path(snapshot_download(repo_id=model_id, allow_patterns=patterns)) + +if output.exists(): + shutil.rmtree(output) +output.mkdir(parents=True) + +for source in snapshot_dir.rglob("*"): + if not source.is_file(): + continue + relative = source.relative_to(snapshot_dir) + target = output / relative + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source, target) +""" + run( + [ + str(python), + "-c", + script, + model_id, + str(output), + "\n".join(OPENVINO_BUNDLE_PATTERNS), + ], + ) + + +def ensure_openvino_tokenizer(venv_dir: Path | None, output: Path) -> None: + if (output / "openvino_tokenizer.xml").is_file(): + return + + if not (output / "tokenizer.json").is_file(): + return + + if venv_dir is None: + convert_tokenizer = Path("convert_tokenizer") + else: + convert_tokenizer = executable_in_venv(venv_dir, "convert_tokenizer") + + run( + [ + str(convert_tokenizer), + str(output), + "--output", + str(output), + "--with-detokenizer", + "--tokenizer-output-type", + "i64", + "--detokenizer-input-type", + "i64", + ], + ) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--model-id", default=DEFAULT_MODEL_ID) + parser.add_argument("--output", required=True, type=Path) + parser.add_argument("--weight-format", default="int4", choices=("int4", "int8", "fp16")) + parser.add_argument("--venv", type=Path) + parser.add_argument("--force", action="store_true") + parser.add_argument( + "--install-deps", + action="store_true", + help="Install/upgrade host-side OpenVINO export dependencies before running optimum-cli.", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + args.output.parent.mkdir(parents=True, exist_ok=True) + if export_is_complete(args.output, args.model_id, args.weight_format) and not args.force: + print(f"Reusing existing OpenVINO model bundle at {args.output}") + return 0 + + python = Path(sys.executable) + optimum_cli = Path("optimum-cli") + if args.venv is not None: + python, optimum_cli = ensure_venv(args.venv) + + if args.install_deps: + run( + [ + str(python), + "-m", + "pip", + "install", + "--upgrade", + "optimum-intel[openvino]", + "openvino-genai", + "openvino-tokenizers", + "huggingface_hub", + ], + ) + + downloaded_openvino_bundle = repo_has_openvino_bundle(args.model_id) + if downloaded_openvino_bundle: + download_openvino_bundle(python, args.model_id, args.output) + ensure_openvino_tokenizer(args.venv, args.output) + else: + run( + [ + str(optimum_cli), + "export", + "openvino", + "--model", + args.model_id, + "--weight-format", + args.weight_format, + str(args.output), + ], + ) + + (args.output / ".openvino_llm_export_complete").write_text( + f"model_id={args.model_id}\nweight_format={args.weight_format}\n", + encoding="utf-8", + ) + action = "Downloaded" if downloaded_openvino_bundle else "Exported" + print(f"{action} {args.model_id} to {args.output}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/ai/src/main/cpp/CMakeLists.txt b/ai/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..b781f5c1 --- /dev/null +++ b/ai/src/main/cpp/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.22.1) + +project(notes_llm) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (NOT DEFINED OPENVINO_GENAI_ANDROID_DIR) + message(FATAL_ERROR "Set -DOPENVINO_GENAI_ANDROID_DIR to an Android OpenVINO GenAI package root.") +endif () + +set(OPENVINO_PREBUILD_INCLUDE_DIRS "") +foreach (include_dir + "${OPENVINO_GENAI_ANDROID_DIR}/include" + "${OPENVINO_GENAI_ANDROID_DIR}/runtime/include") + if (EXISTS "${include_dir}") + list(APPEND OPENVINO_PREBUILD_INCLUDE_DIRS "${include_dir}") + endif () +endforeach () + +if (NOT OPENVINO_PREBUILD_INCLUDE_DIRS) + message(FATAL_ERROR "OpenVINO include directory was not found in ${OPENVINO_GENAI_ANDROID_DIR}.") +endif () + +set(OPENVINO_PREBUILD_LIBRARY_DIRS "") +foreach (library_dir + "${OPENVINO_GENAI_ANDROID_DIR}/android-jni/${ANDROID_ABI}" + "${OPENVINO_GENAI_ANDROID_DIR}/runtime/lib/aarch64" + "${OPENVINO_GENAI_ANDROID_DIR}/lib/${ANDROID_ABI}" + "${OPENVINO_GENAI_ANDROID_DIR}/${ANDROID_ABI}/lib") + if (EXISTS "${library_dir}") + list(APPEND OPENVINO_PREBUILD_LIBRARY_DIRS "${library_dir}") + endif () +endforeach () + +if (NOT OPENVINO_PREBUILD_LIBRARY_DIRS) + message(FATAL_ERROR "OpenVINO library directory was not found in ${OPENVINO_GENAI_ANDROID_DIR}.") +endif () + +add_library( + notes_llm + SHARED + llm_bridge_jni.cpp + llm_engine.cpp +) + +target_include_directories( + notes_llm + PRIVATE + ${OPENVINO_PREBUILD_INCLUDE_DIRS} +) + +target_link_directories( + notes_llm + PRIVATE + ${OPENVINO_PREBUILD_LIBRARY_DIRS} +) + +find_library(log_lib log) + +target_link_libraries( + notes_llm + PRIVATE + openvino_genai + openvino + ${log_lib} +) diff --git a/ai/src/main/cpp/llm_bridge_jni.cpp b/ai/src/main/cpp/llm_bridge_jni.cpp new file mode 100644 index 00000000..b867cda1 --- /dev/null +++ b/ai/src/main/cpp/llm_bridge_jni.cpp @@ -0,0 +1,79 @@ +#include "llm_engine.h" + +#include + +#include +#include +#include + +namespace { + +notes::ai::LlmEngine g_engine; +std::mutex g_engine_mutex; + +std::string to_string(JNIEnv* env, jstring value) { + if (value == nullptr) { + return ""; + } + + const char* chars = env->GetStringUTFChars(value, nullptr); + if (chars == nullptr) { + return ""; + } + + std::string result(chars); + env->ReleaseStringUTFChars(value, chars); + return result; +} + +void throw_illegal_state(JNIEnv* env, const std::string& message) { + jclass exception_class = env->FindClass("java/lang/IllegalStateException"); + if (exception_class != nullptr) { + env->ThrowNew(exception_class, message.c_str()); + } +} + +} // namespace + +extern "C" JNIEXPORT void JNICALL +Java_com_itlab_ai_NativeLlmBridge_init( + JNIEnv* env, + jobject /*thiz*/, + jstring model_dir, + jstring cache_dir, + jstring device +) { + try { + std::lock_guard lock(g_engine_mutex); + g_engine.init(to_string(env, model_dir), to_string(env, cache_dir), to_string(env, device)); + } catch (const std::exception& error) { + throw_illegal_state(env, error.what()); + } +} + +extern "C" JNIEXPORT jstring JNICALL +Java_com_itlab_ai_NativeLlmBridge_generate( + JNIEnv* env, + jobject /*thiz*/, + jstring prompt, + jint max_new_tokens +) { + try { + std::lock_guard lock(g_engine_mutex); + const std::string result = g_engine.generate(to_string(env, prompt), max_new_tokens); + return env->NewStringUTF(result.c_str()); + } catch (const std::exception& error) { + throw_illegal_state(env, error.what()); + return nullptr; + } +} + +extern "C" JNIEXPORT void JNICALL +Java_com_itlab_ai_NativeLlmBridge_close(JNIEnv* env, jobject /*thiz*/) { + try { + std::lock_guard lock(g_engine_mutex); + g_engine.close(); + } catch (const std::exception& error) { + throw_illegal_state(env, error.what()); + } +} diff --git a/ai/src/main/cpp/llm_engine.cpp b/ai/src/main/cpp/llm_engine.cpp new file mode 100644 index 00000000..973d56fb --- /dev/null +++ b/ai/src/main/cpp/llm_engine.cpp @@ -0,0 +1,73 @@ +#include "llm_engine.h" + +#include + +#include "openvino/genai/llm_pipeline.hpp" + +#include +#include +#include +#include + +namespace notes::ai { + +struct LlmEngine::Impl { + explicit Impl(std::unique_ptr llm_pipeline) + : pipeline(std::move(llm_pipeline)) {} + + std::unique_ptr pipeline; + std::mutex mutex; +}; + +LlmEngine::LlmEngine() = default; + +LlmEngine::~LlmEngine() { + // OpenVINO GenAI Android teardown currently crashes after successful generation. + // Keep the process-lifetime pipeline allocated instead of running its destructor. + impl_.release(); +} + +void LlmEngine::init( + const std::string& model_dir, + const std::string& cache_dir, + const std::string& device +) { + if (model_dir.empty()) { + throw std::invalid_argument("OpenVINO LLM model directory is empty."); + } + if (impl_) { + return; + } + + const std::string target_device = device.empty() ? "CPU" : device; + ov::AnyMap pipeline_config; + if (!cache_dir.empty()) { + pipeline_config.insert({ov::cache_dir(cache_dir)}); + } + pipeline_config.insert({ov::hint::inference_precision(ov::element::f32)}); + pipeline_config.insert({ov::hint::dynamic_quantization_group_size(std::numeric_limits::max())}); + + auto pipeline = std::make_unique(model_dir, target_device, pipeline_config); + impl_ = std::make_unique(std::move(pipeline)); +} + +std::string LlmEngine::generate(const std::string& prompt, int max_new_tokens) { + if (!impl_ || !impl_->pipeline) { + throw std::logic_error("OpenVINO GenAI pipeline is not initialized."); + } + if (prompt.empty()) { + return ""; + } + + std::lock_guard lock(impl_->mutex); + ov::genai::GenerationConfig generation_config = impl_->pipeline->get_generation_config(); + generation_config.max_new_tokens = static_cast(max_new_tokens); + generation_config.do_sample = false; + return impl_->pipeline->generate(prompt, generation_config); +} + +void LlmEngine::close() { + // The process owns the GenAI pipeline for the same reason as the destructor above. +} + +} // namespace notes::ai diff --git a/ai/src/main/cpp/llm_engine.h b/ai/src/main/cpp/llm_engine.h new file mode 100644 index 00000000..ce68c86b --- /dev/null +++ b/ai/src/main/cpp/llm_engine.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +namespace notes::ai { + +class LlmEngine { +public: + LlmEngine(); + ~LlmEngine(); + + void init(const std::string& model_dir, const std::string& cache_dir, const std::string& device); + std::string generate(const std::string& prompt, int max_new_tokens); + void close(); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace notes::ai diff --git a/ai/src/main/java/com/itlab/ai/LlmInferenceBackend.kt b/ai/src/main/java/com/itlab/ai/LlmInferenceBackend.kt new file mode 100644 index 00000000..baf6ab73 --- /dev/null +++ b/ai/src/main/java/com/itlab/ai/LlmInferenceBackend.kt @@ -0,0 +1,22 @@ +package com.itlab.ai + +interface LlmInferenceBackend { + fun generate( + prompt: String, + maxNewTokens: Int, + ): String +} + +class MissingLlmRuntimeException( + message: String, + cause: Throwable? = null, +) : IllegalStateException(message, cause) + +class UnavailableLlmBackend( + private val reason: String = "OpenVINO GenAI backend is not configured.", +) : LlmInferenceBackend { + override fun generate( + prompt: String, + maxNewTokens: Int, + ): String = throw MissingLlmRuntimeException(reason) +} diff --git a/ai/src/main/java/com/itlab/ai/NativeLlmBridge.kt b/ai/src/main/java/com/itlab/ai/NativeLlmBridge.kt new file mode 100644 index 00000000..7340d8de --- /dev/null +++ b/ai/src/main/java/com/itlab/ai/NativeLlmBridge.kt @@ -0,0 +1,16 @@ +package com.itlab.ai + +class NativeLlmBridge internal constructor() : AutoCloseable { + external fun init( + modelDir: String, + cacheDir: String, + device: String, + ) + + external fun generate( + prompt: String, + maxNewTokens: Int, + ): String + + external override fun close() +} diff --git a/ai/src/main/java/com/itlab/ai/NoteLlmPromptBuilder.kt b/ai/src/main/java/com/itlab/ai/NoteLlmPromptBuilder.kt new file mode 100644 index 00000000..e80f0825 --- /dev/null +++ b/ai/src/main/java/com/itlab/ai/NoteLlmPromptBuilder.kt @@ -0,0 +1,43 @@ +package com.itlab.ai + +class NoteLlmPromptBuilder( + private val config: OnDeviceLlmConfig = OnDeviceLlmConfig.defaultAndroid(), +) { + fun summaryPrompt(text: String): String = + chatPrompt( + """ + Summarize the note in 1-2 concise sentences. + Return only the summary, without markdown or a preamble. + + Note: + ${trimInput(text)} + """.trimIndent(), + ) + + fun tagsPrompt(text: String): String = + chatPrompt( + """ + Suggest up to ${config.maxTags} short tags for the note. + Return only a comma-separated tag list. Use lowercase tags. + + Note: + ${trimInput(text)} + """.trimIndent(), + ) + + private fun chatPrompt(instruction: String): String = + """ + <|im_start|>system + You are a concise assistant for a notes app. + <|im_end|> + <|im_start|>user + $instruction + <|im_end|> + <|im_start|>assistant + """.trimIndent() + + private fun trimInput(text: String): String = + text + .trim() + .take(config.maxInputChars) +} diff --git a/ai/src/main/java/com/itlab/ai/OnDeviceLlmConfig.kt b/ai/src/main/java/com/itlab/ai/OnDeviceLlmConfig.kt new file mode 100644 index 00000000..265e227e --- /dev/null +++ b/ai/src/main/java/com/itlab/ai/OnDeviceLlmConfig.kt @@ -0,0 +1,34 @@ +package com.itlab.ai + +data class OnDeviceLlmConfig( + val modelId: String, + val assetModelDir: String, + val modelDirName: String, + val device: String, + val nativeLibraryName: String, + val cacheDirName: String, + val maxInputChars: Int, + val summaryMaxNewTokens: Int, + val tagsMaxNewTokens: Int, + val maxTags: Int, + val includeReasoningOutput: Boolean, + val disableReasoningPromptHint: String, +) { + companion object { + fun defaultAndroid(): OnDeviceLlmConfig = + OnDeviceLlmConfig( + modelId = "OpenVINO/Qwen3-0.6B-int4-ov", + assetModelDir = "models/on-device-llm-openvino", + modelDirName = "on-device-llm-openvino", + device = "CPU", + nativeLibraryName = "notes_llm", + cacheDirName = "openvino-genai-cache", + maxInputChars = 6_000, + summaryMaxNewTokens = 96, + tagsMaxNewTokens = 48, + maxTags = 6, + includeReasoningOutput = false, + disableReasoningPromptHint = "/no_think", + ) + } +} diff --git a/ai/src/main/java/com/itlab/ai/OpenVinoEngine.kt b/ai/src/main/java/com/itlab/ai/OpenVinoEngine.kt index 9f55841a..20e31b67 100644 --- a/ai/src/main/java/com/itlab/ai/OpenVinoEngine.kt +++ b/ai/src/main/java/com/itlab/ai/OpenVinoEngine.kt @@ -1,9 +1,23 @@ package com.itlab.ai -class OpenVinoEngine { - fun runLlmSummary(text: String): String = text +class OpenVinoEngine( + private val llmBackend: LlmInferenceBackend = UnavailableLlmBackend(), + private val promptBuilder: NoteLlmPromptBuilder = NoteLlmPromptBuilder(), + private val config: OnDeviceLlmConfig = OnDeviceLlmConfig.defaultAndroid(), +) { + fun runLlmSummary(text: String): String { + if (text.isBlank()) return "" + return llmBackend.generate( + prompt = promptBuilder.summaryPrompt(text), + maxNewTokens = config.summaryMaxNewTokens, + ) + } - fun runLlmTagging(text: String): String = text - - fun runYoloTagging(imageSource: String): String = imageSource + fun runLlmTagging(text: String): String { + if (text.isBlank()) return "" + return llmBackend.generate( + prompt = promptBuilder.tagsPrompt(text), + maxNewTokens = config.tagsMaxNewTokens, + ) + } } diff --git a/ai/src/main/java/com/itlab/ai/OpenVinoGenAiBackend.kt b/ai/src/main/java/com/itlab/ai/OpenVinoGenAiBackend.kt new file mode 100644 index 00000000..be9e1a49 --- /dev/null +++ b/ai/src/main/java/com/itlab/ai/OpenVinoGenAiBackend.kt @@ -0,0 +1,181 @@ +package com.itlab.ai + +import android.content.Context +import java.io.File +import java.io.IOException + +class OpenVinoGenAiBackend( + context: Context, + private val config: OnDeviceLlmConfig = OnDeviceLlmConfig.defaultAndroid(), +) : LlmInferenceBackend, + AutoCloseable { + private val appContext = context.applicationContext + private var bridge: NativeLlmBridge? = null + + @Synchronized + override fun generate( + prompt: String, + maxNewTokens: Int, + ): String { + val activeBridge = bridge ?: createBridge() + val response = activeBridge.generate(preparePrompt(prompt, config), maxNewTokens) + return if (config.includeReasoningOutput) { + response + } else { + stripReasoningSections(response) + } + } + + @Synchronized + override fun close() { + bridge?.close() + bridge = null + } + + private fun createBridge(): NativeLlmBridge { + val modelDir = ensureModelDirectory() + val cacheDir = + File(appContext.cacheDir, config.cacheDirName) + .apply { mkdirs() } + val runtime = + OpenVinoNativeRuntime.prepare( + context = appContext, + notesLibraryName = config.nativeLibraryName, + ) + + val loaded = + runtime + .loadBridge() + .getOrElse { cause -> + throw MissingLlmRuntimeException( + "OpenVINO GenAI native library '${config.nativeLibraryName}' is not packaged.", + cause, + ) + } + + loaded.init( + modelDir = modelDir.absolutePath, + cacheDir = cacheDir.absolutePath, + device = config.device, + ) + bridge = loaded + return loaded + } + + private fun ensureModelDirectory(): File { + val targetDir = File(appContext.filesDir, "models/${config.modelDirName}") + if (!appContext.assetDirectoryExists(config.assetModelDir)) { + throw MissingLlmRuntimeException( + "OpenVINO LLM model assets are missing at assets/${config.assetModelDir}. " + + "Gradle should run :ai:stageOpenVinoLlmAssets during preBuild.", + ) + } + + val assetMarker = appContext.readAssetText("${config.assetModelDir}/$MODEL_MARKER_FILE") + val targetMarker = targetDir.resolve(MODEL_MARKER_FILE).takeIf { it.isFile }?.readText() + if (targetDir.exists() && !targetDir.list().isNullOrEmpty() && assetMarker == targetMarker) { + return targetDir + } + + targetDir.deleteRecursively() + targetDir.mkdirs() + appContext.copyAssetDirectory(config.assetModelDir, targetDir) + File(appContext.cacheDir, config.cacheDirName).deleteRecursively() + return targetDir + } + + private companion object { + const val MODEL_MARKER_FILE = ".openvino_llm_export_complete" + } +} + +internal fun Context.assetDirectoryExists(assetPath: String): Boolean = + try { + !assets.list(assetPath).isNullOrEmpty() + } catch (_: IOException) { + false + } + +internal fun Context.readAssetText(assetPath: String): String? = + try { + assets + .open(assetPath) + .bufferedReader() + .use { it.readText() } + } catch (_: IOException) { + null + } + +internal fun Context.copyAssetDirectory( + assetPath: String, + targetDir: File, +) { + val children = + assets.list(assetPath) + ?: throw MissingLlmRuntimeException("Unable to list model asset directory: $assetPath") + + children.forEach { child -> copyAssetChild(assetPath, targetDir, child) } +} + +internal fun Context.copyAssetChild( + assetPath: String, + targetDir: File, + child: String, +) { + val childAssetPath = "$assetPath/$child" + val childTarget = File(targetDir, child) + val nestedChildren = assets.list(childAssetPath) + if (nestedChildren.isNullOrEmpty()) { + copyAssetFile(childAssetPath, childTarget) + } else { + childTarget.mkdirs() + copyAssetDirectory(childAssetPath, childTarget) + } +} + +internal fun Context.copyAssetFile( + assetPath: String, + targetFile: File, +) { + assets.open(assetPath).use { input -> + targetFile.outputStream().use { output -> + input.copyTo(output) + } + } +} + +internal fun preparePrompt( + prompt: String, + config: OnDeviceLlmConfig, +): String { + val hint = config.disableReasoningPromptHint.trim() + val trimmedPrompt = prompt.trimEnd() + val shouldAppendHint = + !config.includeReasoningOutput && + prompt.isNotBlank() && + hint.isNotEmpty() && + !trimmedPrompt.endsWith(hint, ignoreCase = true) + + return if (shouldAppendHint) { + "$trimmedPrompt\n$hint" + } else { + prompt + } +} + +internal fun stripReasoningSections(response: String): String { + if (response.isBlank()) { + return response + } + + return THINKING_TAG_REGEX + .replace(THINKING_BLOCK_REGEX.replace(response, ""), "") + .trim() +} + +private val THINKING_BLOCK_REGEX = + Regex( + pattern = ".*?", + options = setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL), + ) +private val THINKING_TAG_REGEX = Regex("", RegexOption.IGNORE_CASE) diff --git a/ai/src/main/java/com/itlab/ai/OpenVinoNativeRuntime.kt b/ai/src/main/java/com/itlab/ai/OpenVinoNativeRuntime.kt new file mode 100644 index 00000000..e14eb803 --- /dev/null +++ b/ai/src/main/java/com/itlab/ai/OpenVinoNativeRuntime.kt @@ -0,0 +1,170 @@ +package com.itlab.ai + +import android.annotation.SuppressLint +import android.content.Context +import java.io.File + +internal class OpenVinoNativeRuntime private constructor( + private val runtimeDir: File, + private val notesLibrary: File, +) { + @SuppressLint("UnsafeDynamicallyLoadedCode") + fun loadBridge(): Result = + runCatching { + preferredLibraryLoadOrder + .map { File(runtimeDir, it) } + .filter { it.isFile } + .forEach { System.load(it.absolutePath) } + System.load(notesLibrary.absolutePath) + NativeLlmBridge() + } + + companion object { + private const val RUNTIME_ASSET_DIR = "openvino-runtime" + + private val preferredLibraryLoadOrder = + listOf( + "libc++_shared.so", + "libtbb.so", + "libtbbmalloc.so", + "libtbbmalloc_proxy.so", + "libopenvino.so", + "libopenvino_c.so", + "libopenvino_ir_frontend.so", + "libopenvino_tokenizers.so", + "libopenvino_arm_cpu_plugin.so", + "libopenvino_auto_plugin.so", + "libopenvino_hetero_plugin.so", + "libopenvino_auto_batch_plugin.so", + "libopenvino_genai.so", + "libopenvino_genai_c.so", + ) + + fun prepare( + context: Context, + notesLibraryName: String, + ): OpenVinoNativeRuntime { + val appContext = context.applicationContext + val nativeLibraryDir = + File(appContext.applicationInfo.nativeLibraryDir) + .takeIf { it.isDirectory } + ?: throw MissingLlmRuntimeException( + "Android native library directory is not available. " + + "The app must use legacy JNI packaging for OpenVINO GenAI.", + ) + val notesLibrary = nativeLibraryDir.resolve("lib$notesLibraryName.so") + if (!notesLibrary.isFile) { + throw MissingLlmRuntimeException("Native LLM bridge is missing: ${notesLibrary.absolutePath}") + } + + val runtimeDir = File(appContext.filesDir, "openvino-runtime/${nativeLibraryDir.name}") + runtimeDir.mkdirs() + copyOpenVinoLibraries(nativeLibraryDir, runtimeDir) + copyAssetDirectory(appContext, RUNTIME_ASSET_DIR, runtimeDir) + copyPluginLibrariesToVersionDirs(runtimeDir) + return OpenVinoNativeRuntime(runtimeDir, notesLibrary) + } + + private fun copyOpenVinoLibraries( + nativeLibraryDir: File, + runtimeDir: File, + ) { + val libraryFiles = + nativeLibraryDir.listFiles { file -> + file.isFile && + file.name.endsWith(".so") && + ( + file.name.startsWith("libopenvino") || + file.name.startsWith("libtbb") || + file.name == "libc++_shared.so" + ) + } + val libraries = libraryFiles?.toList().orEmpty() + if (libraries.none { it.name == "libopenvino.so" }) { + throw MissingLlmRuntimeException("OpenVINO runtime library is missing in $nativeLibraryDir") + } + if (libraries.none { it.name == "libopenvino_genai.so" }) { + throw MissingLlmRuntimeException("OpenVINO GenAI library is missing in $nativeLibraryDir") + } + + libraries.forEach { source -> + source.copyToIfChanged(runtimeDir.resolve(source.name)) + } + } + + private fun copyPluginLibrariesToVersionDirs(runtimeDir: File) { + val pluginLibraryFiles = + runtimeDir.listFiles { file -> + file.isFile && + file.name.startsWith("libopenvino_") && + file.name.endsWith("_plugin.so") + } + val pluginLibraries = pluginLibraryFiles?.toList().orEmpty() + if (pluginLibraries.isEmpty()) { + return + } + + runtimeDir + .listFiles { file -> file.isDirectory && file.name.startsWith("openvino-") } + ?.forEach { pluginDir -> + pluginLibraries.forEach { library -> + library.copyToIfChanged(pluginDir.resolve(library.name)) + } + } + } + + private fun copyAssetDirectory( + context: Context, + assetPath: String, + targetDir: File, + ) { + val children = + context.assets.list(assetPath) + ?: throw MissingLlmRuntimeException("Unable to list runtime asset directory: $assetPath") + if (children.isEmpty()) { + throw MissingLlmRuntimeException("OpenVINO runtime assets are missing at assets/$assetPath") + } + + children.forEach { child -> copyAssetChild(context, assetPath, targetDir, child) } + } + + private fun copyAssetChild( + context: Context, + assetPath: String, + targetDir: File, + child: String, + ) { + val childAssetPath = "$assetPath/$child" + val childTarget = targetDir.resolve(child) + val nestedChildren = context.assets.list(childAssetPath) + if (nestedChildren.isNullOrEmpty()) { + copyAssetFile(context, childAssetPath, childTarget) + } else { + childTarget.mkdirs() + copyAssetDirectory(context, childAssetPath, childTarget) + } + } + + private fun copyAssetFile( + context: Context, + assetPath: String, + targetFile: File, + ) { + context.assets.open(assetPath).use { input -> + targetFile.parentFile?.mkdirs() + targetFile.outputStream().use { output -> + input.copyTo(output) + } + } + } + + private fun File.copyToIfChanged(target: File) { + if (target.isFile && target.length() == length()) { + return + } + + target.parentFile?.mkdirs() + copyTo(target, overwrite = true) + } + } +} diff --git a/ai/src/main/java/com/itlab/ai/OpenVinoNoteAiService.kt b/ai/src/main/java/com/itlab/ai/OpenVinoNoteAiService.kt index 299f6a3b..cdf5313d 100644 --- a/ai/src/main/java/com/itlab/ai/OpenVinoNoteAiService.kt +++ b/ai/src/main/java/com/itlab/ai/OpenVinoNoteAiService.kt @@ -1,24 +1,27 @@ package com.itlab.ai import com.itlab.domain.ai.NoteAiService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext class OpenVinoNoteAiService( private val engine: OpenVinoEngine, private val processor: ResultProcessor, ) : NoteAiService { - override suspend fun summarize(text: String): String { - val llmResult = engine.runLlmSummary(text) - return processor.normalizeSummary(llmResult) - } + override suspend fun summarize(text: String): String = + withContext(Dispatchers.Default) { + val llmResult = engine.runLlmSummary(text) + processor.normalizeSummary(llmResult) + } - override suspend fun tagTXT(text: String): Set { - val llmResult = engine.runLlmTagging(text) - return processor.normalizeTags(llmResult) - } + override suspend fun tagTXT(text: String): Set = + withContext(Dispatchers.Default) { + val llmResult = engine.runLlmTagging(text) + processor.normalizeTags(llmResult) + } - override suspend fun tagIMGs(img: List): Set = - img - .map { source -> engine.runYoloTagging(source) } - .flatMap { result -> processor.normalizeTags(result) } - .toSet() + override suspend fun tagIMGs(img: List): Set { + // This is a text LLM path. Image tagging stays in a separate AI direction. + return emptySet() + } } diff --git a/ai/src/main/java/com/itlab/ai/di/AiModule.kt b/ai/src/main/java/com/itlab/ai/di/AiModule.kt new file mode 100644 index 00000000..abff8a0b --- /dev/null +++ b/ai/src/main/java/com/itlab/ai/di/AiModule.kt @@ -0,0 +1,33 @@ +package com.itlab.ai.di + +import com.itlab.ai.LlmInferenceBackend +import com.itlab.ai.NoteLlmPromptBuilder +import com.itlab.ai.OnDeviceLlmConfig +import com.itlab.ai.OpenVinoEngine +import com.itlab.ai.OpenVinoGenAiBackend +import com.itlab.ai.OpenVinoNoteAiService +import com.itlab.ai.ResultProcessor +import com.itlab.domain.ai.NoteAiService +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val aiModule = + module { + single { OnDeviceLlmConfig.defaultAndroid() } + single { NoteLlmPromptBuilder(get()) } + single { OpenVinoGenAiBackend(androidContext(), get()) } + single { ResultProcessor() } + single { + OpenVinoEngine( + llmBackend = get(), + promptBuilder = get(), + config = get(), + ) + } + single { + OpenVinoNoteAiService( + engine = get(), + processor = get(), + ) + } + } diff --git a/ai/src/test/java/com/itlab/ai/OpenVinoAiLayerTest.kt b/ai/src/test/java/com/itlab/ai/OpenVinoAiLayerTest.kt index 452421e3..1491c81a 100644 --- a/ai/src/test/java/com/itlab/ai/OpenVinoAiLayerTest.kt +++ b/ai/src/test/java/com/itlab/ai/OpenVinoAiLayerTest.kt @@ -2,6 +2,8 @@ package com.itlab.ai import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test class OpenVinoAiLayerTest { @@ -35,30 +37,190 @@ class OpenVinoAiLayerTest { @Test fun summarize_returnsTrimmedSummary() = runBlocking { - val service = OpenVinoNoteAiService(OpenVinoEngine(), ResultProcessor()) + val backend = RecordingLlmBackend(" Summary text ") + val service = + OpenVinoNoteAiService( + OpenVinoEngine(llmBackend = backend), + ResultProcessor(), + ) - val result = service.summarize(" Summary text ") + val result = service.summarize("Long note") assertEquals("Summary text", result) + assertEquals(OnDeviceLlmConfig.defaultAndroid().summaryMaxNewTokens, backend.lastMaxNewTokens) + assertTrue(backend.lastPrompt.orEmpty().contains("<|im_start|>user")) + assertTrue(backend.lastPrompt.orEmpty().contains("Summarize the note")) + assertTrue(backend.lastPrompt.orEmpty().contains("Long note")) } @Test fun tagTXT_normalizesCaseAndSeparators() = runBlocking { - val service = OpenVinoNoteAiService(OpenVinoEngine(), ResultProcessor()) + val backend = RecordingLlmBackend(" Kotlin, Notes\nAI ") + val service = + OpenVinoNoteAiService( + OpenVinoEngine(llmBackend = backend), + ResultProcessor(), + ) - val result = service.tagTXT(" Kotlin, Notes\nAI ") + val result = service.tagTXT("OpenVINO note") assertEquals(setOf("kotlin", "notes", "ai"), result) + assertEquals(OnDeviceLlmConfig.defaultAndroid().tagsMaxNewTokens, backend.lastMaxNewTokens) + assertTrue(backend.lastPrompt.orEmpty().contains("Suggest up to")) + assertTrue(backend.lastPrompt.orEmpty().contains("OpenVINO note")) } @Test - fun tagIMGs_aggregatesAndDeduplicatesTags() = + fun tagIMGs_returnsEmptySetBecauseVisionIsSeparateFromTextLlm() = runBlocking { - val service = OpenVinoNoteAiService(OpenVinoEngine(), ResultProcessor()) + val service = + OpenVinoNoteAiService( + OpenVinoEngine(llmBackend = RecordingLlmBackend("unused")), + ResultProcessor(), + ) val result = service.tagIMGs(listOf("Cat, Pet", "pet, animal", " CAT")) - assertEquals(setOf("cat", "pet", "animal"), result) + assertEquals(emptySet(), result) } + + @Test + fun noteLlmPromptBuilder_trimsLargeInput() { + val config = OnDeviceLlmConfig.defaultAndroid().copy(maxInputChars = 5) + val builder = NoteLlmPromptBuilder(config) + + val prompt = builder.summaryPrompt("123456789") + + assertTrue(prompt.contains("12345")) + assertTrue(!prompt.contains("123456")) + } + + @Test + fun openVinoEngine_returnsEmptyResultForBlankInputWithoutCallingBackend() { + val backend = RecordingLlmBackend("unused") + val engine = OpenVinoEngine(llmBackend = backend) + + assertEquals("", engine.runLlmSummary(" ")) + assertEquals("", engine.runLlmTagging("\n\t")) + assertEquals(null, backend.lastPrompt) + } + + @Test + fun unavailableBackend_failsWithConfiguredReason() { + val backend = UnavailableLlmBackend("missing runtime") + + val failure = + runCatching { + backend.generate("prompt", maxNewTokens = 1) + }.exceptionOrNull() + + assertTrue(failure is MissingLlmRuntimeException) + assertEquals("missing runtime", failure?.message) + } + + @Test + fun missingRuntimeException_preservesCause() { + val cause = IllegalArgumentException("native loader") + + val failure = MissingLlmRuntimeException("runtime failed", cause) + + assertEquals("runtime failed", failure.message) + assertEquals(cause, failure.cause) + } + + @Test + fun preparePrompt_appendsNoThinkHintWhenReasoningOutputIsDisabled() { + val config = + OnDeviceLlmConfig.defaultAndroid().copy( + includeReasoningOutput = false, + disableReasoningPromptHint = "/no_think", + ) + + val prompt = preparePrompt("Say ok ", config) + + assertEquals("Say ok\n/no_think", prompt) + } + + @Test + fun preparePrompt_keepsPromptWhenHintAlreadyExists() { + val config = OnDeviceLlmConfig.defaultAndroid() + + val prompt = preparePrompt("Say ok\n/NO_THINK", config) + + assertEquals("Say ok\n/NO_THINK", prompt) + } + + @Test + fun preparePrompt_keepsPromptWhenReasoningOutputIsEnabled() { + val config = + OnDeviceLlmConfig.defaultAndroid().copy( + includeReasoningOutput = true, + disableReasoningPromptHint = "/no_think", + ) + + val prompt = preparePrompt("Say ok", config) + + assertEquals("Say ok", prompt) + } + + @Test + fun preparePrompt_keepsBlankPrompt() { + val config = OnDeviceLlmConfig.defaultAndroid() + + val prompt = preparePrompt(" ", config) + + assertEquals(" ", prompt) + } + + @Test + fun stripReasoningSections_removesThinkingBlockAndTags() { + val response = + """ + + hidden chain + + + ok + """.trimIndent() + + val cleaned = stripReasoningSections(response) + + assertEquals("ok", cleaned) + assertFalse(cleaned.contains("think", ignoreCase = true)) + } + + @Test + fun stripReasoningSections_removesDanglingThinkingTags() { + val cleaned = stripReasoningSections("ok") + + assertEquals("ok", cleaned) + } + + @Test + fun stripReasoningSections_keepsBlankResponse() { + val response = " " + + val cleaned = stripReasoningSections(response) + + assertEquals(response, cleaned) + } + + private class RecordingLlmBackend( + private val response: String, + ) : LlmInferenceBackend { + var lastPrompt: String? = null + private set + var lastMaxNewTokens: Int? = null + private set + + override fun generate( + prompt: String, + maxNewTokens: Int, + ): String { + lastPrompt = prompt + lastMaxNewTokens = maxNewTokens + return response + } + } } diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0c8746f8..98c818e2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,6 +38,11 @@ android { buildFeatures { compose = true } + packaging { + jniLibs { + useLegacyPackaging = true + } + } testOptions { managedDevices { localDevices { diff --git a/app/src/androidTest/java/com/itlab/notes/OpenVinoLlmInstrumentedTest.kt b/app/src/androidTest/java/com/itlab/notes/OpenVinoLlmInstrumentedTest.kt new file mode 100644 index 00000000..862b1ad7 --- /dev/null +++ b/app/src/androidTest/java/com/itlab/notes/OpenVinoLlmInstrumentedTest.kt @@ -0,0 +1,39 @@ +package com.itlab.notes + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.platform.app.InstrumentationRegistry +import com.itlab.ai.OpenVinoGenAiBackend +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@LargeTest +@RunWith(AndroidJUnit4::class) +class OpenVinoLlmInstrumentedTest { + @Test(timeout = 10 * 60 * 1000) + fun backendGeneratesTextOnDevice() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val prompt = "Reply with one short word: ok" + + OpenVinoGenAiBackend(context).use { backend -> + val response = backend.generate(prompt, maxNewTokens = 8) + val normalizedResponse = + response + .lowercase() + .replace(Regex("[^a-z]+"), " ") + .trim() + + assertTrue("OpenVINO LLM response must not be blank", response.isNotBlank()) + assertFalse( + "OpenVINO LLM response must not expose reasoning tags: $response", + response.contains(" { - directoriesScreen( - directories = state.directories, - onCreateDirectory = { name -> - viewModel.onEvent(NotesUiEvent.CreateDirectory(name)) - }, - onDeleteDirectory = { directory -> - viewModel.onEvent(NotesUiEvent.DeleteDirectory(directory.id)) - }, - onRenameDirectory = { directory, newName -> - viewModel.onEvent(NotesUiEvent.RenameDirectory(directory.id, newName)) + NotesUiScreen.Directories -> directoriesRoute(state, viewModel) + + is NotesUiScreen.DirectoryNotes -> notesListRoute(screen, state, viewModel) + + is NotesUiScreen.NoteEditor -> editorRoute(screen, state, viewModel) + } +} + +@Composable +private fun directoriesRoute( + state: NotesUiState, + viewModel: NotesViewModel, +) { + directoriesScreen( + directories = state.directories, + onCreateDirectory = { name -> + viewModel.onEvent(NotesUiEvent.CreateDirectory(name)) + }, + onDeleteDirectory = { directory -> + viewModel.onEvent(NotesUiEvent.DeleteDirectory(directory.id)) + }, + onRenameDirectory = { directory, newName -> + viewModel.onEvent(NotesUiEvent.RenameDirectory(directory.id, newName)) + }, + onDirectoryClick = { directory -> + viewModel.onEvent(NotesUiEvent.OpenDirectory(directory)) + }, + ) +} + +@Composable +private fun notesListRoute( + screen: NotesUiScreen.DirectoryNotes, + state: NotesUiState, + viewModel: NotesViewModel, +) { + notesListScreen( + directoryName = screen.directory.name, + notes = state.notes, + directories = state.directories.filter { it.id != "all" }, + actions = + NotesListActions( + onBack = { viewModel.onEvent(NotesUiEvent.BackToDirectories) }, + onAddNoteClick = { viewModel.onEvent(NotesUiEvent.CreateNote) }, + onNoteDelete = { note -> viewModel.onEvent(NotesUiEvent.DeleteNote(note.id)) }, + onNoteMove = { noteId, directoryId -> + viewModel.onEvent( + NotesUiEvent.MoveNoteToDirectory( + noteId = noteId, + targetDirectoryId = directoryId, + ), + ) }, - onDirectoryClick = { directory -> - viewModel.onEvent(NotesUiEvent.OpenDirectory(directory)) + onNoteClick = { note -> + viewModel.onEvent(NotesUiEvent.OpenNote(note)) }, - ) - } - - is NotesUiScreen.DirectoryNotes -> { - notesListScreen( - directoryName = screen.directory.name, - notes = state.notes, - directories = state.directories.filter { it.id != "all" }, - actions = - NotesListActions( - onBack = { viewModel.onEvent(NotesUiEvent.BackToDirectories) }, - onAddNoteClick = { viewModel.onEvent(NotesUiEvent.CreateNote) }, - onNoteDelete = { note -> viewModel.onEvent(NotesUiEvent.DeleteNote(note.id)) }, - onNoteMove = { noteId, directoryId -> - viewModel.onEvent( - NotesUiEvent.MoveNoteToDirectory( - noteId = noteId, - targetDirectoryId = directoryId, - ), - ) - }, - onNoteClick = { note -> - viewModel.onEvent(NotesUiEvent.OpenNote(note)) - }, - ), - ) - } + ), + ) +} - is NotesUiScreen.NoteEditor -> { - editorScreen( - directoryName = screen.directory.name, - note = screen.note, +@Composable +private fun editorRoute( + screen: NotesUiScreen.NoteEditor, + state: NotesUiState, + viewModel: NotesViewModel, +) { + editorScreen( + directoryName = screen.directory.name, + note = screen.note, + aiState = state.aiState, + actions = + EditorScreenActions( onBack = { viewModel.onEvent(NotesUiEvent.BackToDirectoryNotes) }, - onSave = { updated -> - viewModel.onEvent(NotesUiEvent.SaveNote(updated)) - }, - ) - } - } + onSave = { updated -> viewModel.onEvent(NotesUiEvent.SaveNote(updated)) }, + onSuggestSummary = { updated -> viewModel.onEvent(NotesUiEvent.SuggestSummary(updated)) }, + onSuggestTags = { updated -> viewModel.onEvent(NotesUiEvent.SuggestTags(updated)) }, + ), + ) } diff --git a/app/src/main/java/com/itlab/notes/ui/NotesUiContract.kt b/app/src/main/java/com/itlab/notes/ui/NotesUiContract.kt index 054570f4..2dbbd9ba 100644 --- a/app/src/main/java/com/itlab/notes/ui/NotesUiContract.kt +++ b/app/src/main/java/com/itlab/notes/ui/NotesUiContract.kt @@ -24,6 +24,13 @@ data class NotesUiState( val screen: NotesUiScreen = NotesUiScreen.Directories, val directories: List = emptyList(), val notes: List = emptyList(), + val aiState: AiUiState = AiUiState(), +) + +data class AiUiState( + val isGeneratingSummary: Boolean = false, + val isGeneratingTags: Boolean = false, + val errorMessage: String? = null, ) sealed interface NotesUiEvent { @@ -53,6 +60,14 @@ sealed interface NotesUiEvent { val note: NoteItemUi, ) : NotesUiEvent + data class SuggestSummary( + val note: NoteItemUi, + ) : NotesUiEvent + + data class SuggestTags( + val note: NoteItemUi, + ) : NotesUiEvent + data class DeleteNote( val noteId: String, ) : NotesUiEvent diff --git a/app/src/main/java/com/itlab/notes/ui/NotesUseCases.kt b/app/src/main/java/com/itlab/notes/ui/NotesUseCases.kt index 7b2ffc16..f06d45ad 100644 --- a/app/src/main/java/com/itlab/notes/ui/NotesUseCases.kt +++ b/app/src/main/java/com/itlab/notes/ui/NotesUseCases.kt @@ -1,10 +1,14 @@ package com.itlab.notes.ui +import com.itlab.domain.usecase.aiusecase.SuggestSummaryUseCase +import com.itlab.domain.usecase.aiusecase.SuggestTagsUseCase import com.itlab.domain.usecase.folderusecase.CreateFolderUseCase import com.itlab.domain.usecase.folderusecase.DeleteFolderUseCase import com.itlab.domain.usecase.folderusecase.GetFolderUseCase import com.itlab.domain.usecase.folderusecase.ObserveFoldersUseCase import com.itlab.domain.usecase.folderusecase.UpdateFolderUseCase +import com.itlab.domain.usecase.noteusecase.ApplySummaryUseCase +import com.itlab.domain.usecase.noteusecase.ApplyTagsUseCase import com.itlab.domain.usecase.noteusecase.CreateNoteUseCase import com.itlab.domain.usecase.noteusecase.DeleteNoteUseCase import com.itlab.domain.usecase.noteusecase.GetUserIdUseCase @@ -25,5 +29,9 @@ data class NotesUseCases( val getFolderUseCase: GetFolderUseCase, val moveNoteToFolderUseCase: MoveNoteToFolderUseCase, val observeNotesUseCase: ObserveNotesUseCase, + val suggestSummaryUseCase: SuggestSummaryUseCase, + val suggestTagsUseCase: SuggestTagsUseCase, + val applySummaryUseCase: ApplySummaryUseCase, + val applyTagsUseCase: ApplyTagsUseCase, val getUserIdUseCase: GetUserIdUseCase, ) diff --git a/app/src/main/java/com/itlab/notes/ui/NotesViewModel.kt b/app/src/main/java/com/itlab/notes/ui/NotesViewModel.kt index bc25d8ce..1cbde31d 100644 --- a/app/src/main/java/com/itlab/notes/ui/NotesViewModel.kt +++ b/app/src/main/java/com/itlab/notes/ui/NotesViewModel.kt @@ -42,64 +42,127 @@ class NotesViewModel( } override fun onEvent(event: NotesUiEvent) { + when (event) { + is NotesUiEvent.OpenDirectory, + NotesUiEvent.BackToDirectories, + is NotesUiEvent.OpenNote, + NotesUiEvent.CreateNote, + NotesUiEvent.BackToDirectoryNotes, + -> handleNavigationEvent(event) + + is NotesUiEvent.CreateDirectory, + is NotesUiEvent.RenameDirectory, + is NotesUiEvent.DeleteDirectory, + -> handleDirectoryEvent(event) + + is NotesUiEvent.MoveNoteToDirectory, + is NotesUiEvent.DeleteNote, + -> handleNoteEvent(event) + + is NotesUiEvent.SaveNote, + is NotesUiEvent.SuggestSummary, + is NotesUiEvent.SuggestTags, + -> handleEditorEvent(event) + } + } + + private fun handleNavigationEvent(event: NotesUiEvent) { when (event) { is NotesUiEvent.OpenDirectory -> openDirectory(event.directory) NotesUiEvent.BackToDirectories -> backToDirectories() is NotesUiEvent.OpenNote -> openNote(event.note) NotesUiEvent.CreateNote -> createNote() - is NotesUiEvent.CreateDirectory -> { - val normalized = event.name.trim() - if (normalized.isNotBlank()) { - viewModelScope.launch { - useCases.createFolderUseCase(NoteFolder(name = normalized)) - } - } - } + NotesUiEvent.BackToDirectoryNotes -> backToDirectoryNotes() + else -> Unit + } + } + + private fun handleDirectoryEvent(event: NotesUiEvent) { + when (event) { + is NotesUiEvent.CreateDirectory -> createDirectory(event.name) is NotesUiEvent.RenameDirectory -> renameDirectory(event) is NotesUiEvent.DeleteDirectory -> deleteDirectory(event.directoryId) - is NotesUiEvent.MoveNoteToDirectory -> { - if (event.targetDirectoryId == "all") return - viewModelScope.launch { - useCases.moveNoteToFolderUseCase( - folderId = event.targetDirectoryId, - noteId = event.noteId, - ) - } - } - NotesUiEvent.BackToDirectoryNotes -> backToDirectoryNotes() - is NotesUiEvent.SaveNote -> saveNote(event.note) + else -> Unit + } + } + + private fun handleNoteEvent(event: NotesUiEvent) { + when (event) { + is NotesUiEvent.MoveNoteToDirectory -> moveNoteToDirectory(event) is NotesUiEvent.DeleteNote -> { viewModelScope.launch { useCases.deleteNoteUseCase(event.noteId) } } + else -> Unit } } - private fun renameDirectory(event: NotesUiEvent.RenameDirectory) { + private fun handleEditorEvent(event: NotesUiEvent) { + when (event) { + is NotesUiEvent.SaveNote -> saveNote(event.note) + is NotesUiEvent.SuggestSummary -> suggestAi(event.note, AiSuggestion.Summary) + is NotesUiEvent.SuggestTags -> suggestAi(event.note, AiSuggestion.Tags) + else -> Unit + } + } + + private val createDirectory: (String) -> Unit = { name -> + val normalized = name.trim() + if (normalized.isNotBlank()) { + viewModelScope.launch { + useCases.createFolderUseCase(NoteFolder(name = normalized)) + } + } + } + + private val renameDirectory: (NotesUiEvent.RenameDirectory) -> Unit = { event -> val normalized = event.newName.trim() - if (normalized.isBlank() || event.directoryId == "all") return - viewModelScope.launch { - val existingFolder = useCases.getFolderUseCase(event.directoryId) ?: return@launch - useCases.updateFolderUseCase(existingFolder.copy(name = normalized)) + if (normalized.isNotBlank() && event.directoryId != "all") { + viewModelScope.launch { + val existingFolder = useCases.getFolderUseCase(event.directoryId) ?: return@launch + useCases.updateFolderUseCase(existingFolder.copy(name = normalized)) + } } } - private fun deleteDirectory(directoryId: String) { - if (directoryId == "all") return - viewModelScope.launch { - useCases.deleteFolderUseCase(directoryId) - if ((uiState.screen as? NotesUiScreen.DirectoryNotes)?.directory?.id == directoryId) { - backToDirectories() + private val backToDirectories: () -> Unit = { + uiState = + uiState.copy( + screen = NotesUiScreen.Directories, + notes = emptyList(), + aiState = AiUiState(), + ) + } + + private val deleteDirectory: (String) -> Unit = { directoryId -> + if (directoryId != "all") { + viewModelScope.launch { + useCases.deleteFolderUseCase(directoryId) + if ((uiState.screen as? NotesUiScreen.DirectoryNotes)?.directory?.id == directoryId) { + backToDirectories() + } + } + } + } + + private val moveNoteToDirectory: (NotesUiEvent.MoveNoteToDirectory) -> Unit = { event -> + if (event.targetDirectoryId != "all") { + viewModelScope.launch { + useCases.moveNoteToFolderUseCase( + folderId = event.targetDirectoryId, + noteId = event.noteId, + ) } } } - private fun openDirectory(directory: DirectoryItemUi) { + private val openDirectory: (DirectoryItemUi) -> Unit = { directory -> uiState = uiState.copy( screen = NotesUiScreen.DirectoryNotes(directory = directory), notes = emptyList(), + aiState = AiUiState(), ) notesJob?.cancel() val isAll = directory.id == "all" @@ -113,37 +176,36 @@ class NotesViewModel( } flow.collect { notes -> + val updatedDirectory = directory.copy(noteCount = notes.size) + val currentScreen = uiState.screen uiState = uiState.copy( notes = notes.map { it.toUi() }, screen = - NotesUiScreen.DirectoryNotes( - directory = directory.copy(noteCount = notes.size), - ), + if (currentScreen is NotesUiScreen.DirectoryNotes && + currentScreen.directory.id == directory.id + ) { + NotesUiScreen.DirectoryNotes(directory = updatedDirectory) + } else { + currentScreen + }, ) } } } - private val backToDirectories: () -> Unit = { - uiState = - uiState.copy( - screen = NotesUiScreen.Directories, - notes = emptyList(), - ) - } - - private fun openNote(note: NoteItemUi) { + private val openNote: (NoteItemUi) -> Unit = { note -> val dir = (uiState.screen as? NotesUiScreen.DirectoryNotes)?.directory if (dir != null) { uiState = uiState.copy( screen = NotesUiScreen.NoteEditor(directory = dir, note = note), + aiState = AiUiState(), ) } } - private fun createNote() { + private val createNote: () -> Unit = { val dir = (uiState.screen as? NotesUiScreen.DirectoryNotes)?.directory if (dir != null) { val newNote = @@ -154,33 +216,100 @@ class NotesViewModel( uiState = uiState.copy( screen = NotesUiScreen.NoteEditor(directory = dir, note = newNote), + aiState = AiUiState(), ) } } - private fun backToDirectoryNotes() { + private val backToDirectoryNotes: () -> Unit = { val editor = uiState.screen as? NotesUiScreen.NoteEditor if (editor != null) { - uiState = uiState.copy(screen = NotesUiScreen.DirectoryNotes(directory = editor.directory)) + uiState = + uiState.copy( + screen = NotesUiScreen.DirectoryNotes(directory = editor.directory), + aiState = AiUiState(), + ) } } private fun saveNote(note: NoteItemUi) { val editor = uiState.screen as? NotesUiScreen.NoteEditor ?: return viewModelScope.launch { - val userId = useCases.getUserIdUseCase() ?: "anonymous_user" - val targetFolderId = note.folderId ?: editor.directory.id.asDomainFolderId() - val existing = latestNotes.firstOrNull { it.id == note.id } - if (existing != null) { - useCases.updateNoteUseCase(existing.applyUiUpdate(note, targetFolderId)) - } else { - useCases.createNoteUseCase(note.toDomain(userId = userId, folderId = targetFolderId)) - } - uiState = uiState.copy(screen = NotesUiScreen.DirectoryNotes(directory = editor.directory)) + upsertEditorNote(note, editor, latestNotes, useCases) + .onSuccess { + uiState = + uiState.copy( + screen = NotesUiScreen.DirectoryNotes(directory = editor.directory), + aiState = AiUiState(), + ) + }.onFailure { error -> + updateAiState { it.copy(errorMessage = error.userMessage("Unable to save note")) } + } } } - private fun recomputeDirectories() { + private fun suggestAi( + note: NoteItemUi, + suggestion: AiSuggestion, + ) { + val editor = uiState.screen as? NotesUiScreen.NoteEditor ?: return + viewModelScope.launch { + updateAiState { suggestion.startState(it) } + val savedNote = + upsertEditorNote(note, editor, latestNotes, useCases) + .getOrElse { error -> + updateAiState { suggestion.errorState(it, error) } + return@launch + } + updateEditorNote(savedNote) + + val generated = + when (suggestion) { + AiSuggestion.Summary -> + useCases + .suggestSummaryUseCase(savedNote.id) + .mapCatching { summary -> + useCases.applySummaryUseCase(savedNote.id, summary).getOrThrow() + savedNote.copy(summary = summary) + } + AiSuggestion.Tags -> + useCases + .suggestTagsUseCase(savedNote.id) + .mapCatching { tags -> + useCases.applyTagsUseCase(savedNote.id, tags).getOrThrow() + savedNote.copy(tags = tags) + } + } + + generated + .onSuccess { updatedNote -> + updateEditorNote(updatedNote) + updateAiState { suggestion.successState(it) } + }.onFailure { error -> + updateAiState { suggestion.errorState(it, error) } + } + } + } + + private val updateEditorNote: (NoteItemUi) -> Unit = { note -> + val editor = uiState.screen as? NotesUiScreen.NoteEditor + if (editor != null) { + uiState = + uiState.copy( + screen = + NotesUiScreen.NoteEditor( + directory = editor.directory, + note = note, + ), + ) + } + } + + private val updateAiState: ((AiUiState) -> AiUiState) -> Unit = { update -> + uiState = uiState.copy(aiState = update(uiState.aiState)) + } + + private val recomputeDirectories: () -> Unit = { val countsByFolderId = latestNotes.groupingBy { it.folderId }.eachCount() val allNotesCount = latestNotes.size @@ -211,6 +340,59 @@ class NotesViewModel( } } +internal enum class AiSuggestion { + Summary, + Tags, +} + +internal fun AiSuggestion.startState(state: AiUiState): AiUiState = + when (this) { + AiSuggestion.Summary -> state.copy(isGeneratingSummary = true, errorMessage = null) + AiSuggestion.Tags -> state.copy(isGeneratingTags = true, errorMessage = null) + } + +internal fun AiSuggestion.successState(state: AiUiState): AiUiState = + when (this) { + AiSuggestion.Summary -> state.copy(isGeneratingSummary = false, errorMessage = null) + AiSuggestion.Tags -> state.copy(isGeneratingTags = false, errorMessage = null) + } + +internal fun AiSuggestion.errorState( + state: AiUiState, + error: Throwable, +): AiUiState = + when (this) { + AiSuggestion.Summary -> + state.copy( + isGeneratingSummary = false, + errorMessage = error.userMessage("Unable to generate summary"), + ) + AiSuggestion.Tags -> + state.copy( + isGeneratingTags = false, + errorMessage = error.userMessage("Unable to suggest tags"), + ) + } + +internal suspend fun upsertEditorNote( + note: NoteItemUi, + editor: NotesUiScreen.NoteEditor, + latestNotes: List, + useCases: NotesUseCases, +): Result = + runCatching { + val targetFolderId = note.folderId ?: editor.directory.id.asDomainFolderId() + val existing = latestNotes.firstOrNull { it.id == note.id } + if (existing != null) { + useCases.updateNoteUseCase(existing.applyUiUpdate(note, targetFolderId)).getOrThrow() + note.copy(folderId = targetFolderId) + } else { + val userId = useCases.getUserIdUseCase() ?: "anonymous_user" + val savedId = useCases.createNoteUseCase(note.toDomain(userId, targetFolderId)).getOrThrow() + note.copy(id = savedId, folderId = targetFolderId) + } + } + internal fun NoteFolder.toUi(noteCount: Int): DirectoryItemUi = DirectoryItemUi(id = id, name = name, noteCount = noteCount) @@ -223,6 +405,8 @@ internal fun Note.toUi(): NoteItemUi = .filterIsInstance() .joinToString("\n") { it.text }, folderId = folderId, + tags = tags, + summary = summary, ) internal fun NoteItemUi.toDomain( @@ -235,6 +419,8 @@ internal fun NoteItemUi.toDomain( folderId = folderId, contentItems = listOf(ContentItem.Text(text = content)), userId = userId, + tags = tags, + summary = summary, ) internal fun Note.applyUiUpdate( @@ -251,7 +437,11 @@ internal fun Note.applyUiUpdate( title = ui.title, folderId = targetFolderId, contentItems = if (updatedText != null) nonTextContent + updatedText else nonTextContent, + tags = ui.tags, + summary = ui.summary, ) } internal fun String.asDomainFolderId(): String? = if (this == "all") null else this + +internal fun Throwable.userMessage(fallback: String): String = message ?: fallback diff --git a/app/src/main/java/com/itlab/notes/ui/editor/EditorAiPanel.kt b/app/src/main/java/com/itlab/notes/ui/editor/EditorAiPanel.kt new file mode 100644 index 00000000..d74007c9 --- /dev/null +++ b/app/src/main/java/com/itlab/notes/ui/editor/EditorAiPanel.kt @@ -0,0 +1,146 @@ +package com.itlab.notes.ui.editor + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.itlab.notes.ui.AiUiState + +internal data class EditorAiPanelState( + val summary: String?, + val tags: Set, + val aiState: AiUiState, +) + +internal data class EditorAiPanelActions( + val onSuggestSummary: () -> Unit, + val onSuggestTags: () -> Unit, +) + +@Composable +internal fun editorAiPanel( + state: EditorAiPanelState, + actions: EditorAiPanelActions, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + editorAiActionsRow(state, actions) + editorAiError(state.aiState.errorMessage) + editorSummaryCard(state.summary) + editorTagsText(state.tags) + } +} + +@Composable +private fun editorAiActionsRow( + state: EditorAiPanelState, + actions: EditorAiPanelActions, +) { + val isGenerating = state.aiState.isGeneratingSummary || state.aiState.isGeneratingTags + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Button( + onClick = actions.onSuggestSummary, + enabled = !isGenerating, + modifier = Modifier.weight(1f), + ) { + summaryButtonContent(state.aiState.isGeneratingSummary) + } + + OutlinedButton( + onClick = actions.onSuggestTags, + enabled = !isGenerating, + modifier = Modifier.weight(1f), + ) { + tagsButtonContent(state.aiState.isGeneratingTags) + } + } +} + +@Composable +private fun summaryButtonContent(isGenerating: Boolean) { + val colors = MaterialTheme.colorScheme + if (isGenerating) { + CircularProgressIndicator( + modifier = Modifier.size(16.dp), + strokeWidth = 2.dp, + color = colors.onPrimary, + ) + } else { + Text("Summarize") + } +} + +@Composable +private fun tagsButtonContent(isGenerating: Boolean) { + if (isGenerating) { + CircularProgressIndicator( + modifier = Modifier.size(16.dp), + strokeWidth = 2.dp, + ) + } else { + Text("Suggest tags") + } +} + +@Composable +private fun editorAiError(errorMessage: String?) { + val colors = MaterialTheme.colorScheme + errorMessage?.let { error -> + Text( + text = error, + color = colors.error, + style = MaterialTheme.typography.bodySmall, + ) + } +} + +@Composable +private fun editorSummaryCard(summary: String?) { + if (!summary.isNullOrBlank()) { + val colors = MaterialTheme.colorScheme + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(12.dp)) { + Text( + text = "Summary", + color = colors.onSurfaceVariant, + style = MaterialTheme.typography.labelMedium, + ) + Text( + text = summary, + color = colors.onSurface, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(top = 4.dp), + ) + } + } + } +} + +@Composable +private fun editorTagsText(tags: Set) { + if (tags.isNotEmpty()) { + val colors = MaterialTheme.colorScheme + Text( + text = "Tags: ${tags.joinToString(", ")}", + color = colors.onSurfaceVariant, + style = MaterialTheme.typography.bodyMedium, + ) + } +} diff --git a/app/src/main/java/com/itlab/notes/ui/editor/EditorScreen.kt b/app/src/main/java/com/itlab/notes/ui/editor/EditorScreen.kt index aa826b2a..a866dd42 100644 --- a/app/src/main/java/com/itlab/notes/ui/editor/EditorScreen.kt +++ b/app/src/main/java/com/itlab/notes/ui/editor/EditorScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import com.itlab.notes.ui.AiUiState import com.itlab.notes.ui.notes.NoteItemUi @OptIn(ExperimentalMaterial3Api::class) @@ -29,8 +30,8 @@ import com.itlab.notes.ui.notes.NoteItemUi fun editorScreen( directoryName: String, note: NoteItemUi, - onBack: () -> Unit, - onSave: (NoteItemUi) -> Unit, + aiState: AiUiState, + actions: EditorScreenActions, ) { val colors = MaterialTheme.colorScheme val editorVm = remember(note.id) { EditorViewModel(initialNote = note) } @@ -41,20 +42,31 @@ fun editorScreen( editorTopBar( directoryName = directoryName, title = editorVm.title, - onBack = onBack, + onBack = actions.onBack, ) }, floatingActionButton = { editorFab( - onClick = { onSave(editorVm.buildUpdatedNote()) }, + onClick = { actions.onSave(editorVm.buildUpdatedNote()) }, ) }, ) { paddingValues -> editorContent( - title = editorVm.title, - content = editorVm.content, - onTitleChange = editorVm::onTitleChange, - onContentChange = editorVm::onContentChange, + state = + EditorContentState( + title = editorVm.title, + content = editorVm.content, + summary = note.summary, + tags = note.tags, + aiState = aiState, + ), + actions = + EditorContentActions( + onTitleChange = editorVm::onTitleChange, + onContentChange = editorVm::onContentChange, + onSuggestSummary = { actions.onSuggestSummary(editorVm.buildUpdatedNote()) }, + onSuggestTags = { actions.onSuggestTags(editorVm.buildUpdatedNote()) }, + ), modifier = Modifier.padding(paddingValues), ) } @@ -110,15 +122,27 @@ private fun editorFab(onClick: () -> Unit) { } } +private data class EditorContentState( + val title: String, + val content: String, + val summary: String?, + val tags: Set, + val aiState: AiUiState, +) + +private data class EditorContentActions( + val onTitleChange: (String) -> Unit, + val onContentChange: (String) -> Unit, + val onSuggestSummary: () -> Unit, + val onSuggestTags: () -> Unit, +) + @Composable private fun editorContent( - title: String, - content: String, - onTitleChange: (String) -> Unit, - onContentChange: (String) -> Unit, + state: EditorContentState, + actions: EditorContentActions, modifier: Modifier = Modifier, ) { - val colors = MaterialTheme.colorScheme Column( modifier = modifier @@ -126,15 +150,30 @@ private fun editorContent( .padding(horizontal = 16.dp, vertical = 12.dp), ) { editorTitleField( - value = title, - onValueChange = onTitleChange, + value = state.title, + onValueChange = actions.onTitleChange, ) editorContentField( - value = content, - onValueChange = onContentChange, + value = state.content, + onValueChange = actions.onContentChange, modifier = Modifier.padding(top = 12.dp), ) + + editorAiPanel( + state = + EditorAiPanelState( + summary = state.summary, + tags = state.tags, + aiState = state.aiState, + ), + actions = + EditorAiPanelActions( + onSuggestSummary = actions.onSuggestSummary, + onSuggestTags = actions.onSuggestTags, + ), + modifier = Modifier.padding(top = 16.dp), + ) } } diff --git a/app/src/main/java/com/itlab/notes/ui/editor/EditorScreenActions.kt b/app/src/main/java/com/itlab/notes/ui/editor/EditorScreenActions.kt new file mode 100644 index 00000000..9d564cf5 --- /dev/null +++ b/app/src/main/java/com/itlab/notes/ui/editor/EditorScreenActions.kt @@ -0,0 +1,10 @@ +package com.itlab.notes.ui.editor + +import com.itlab.notes.ui.notes.NoteItemUi + +data class EditorScreenActions( + val onBack: () -> Unit, + val onSave: (NoteItemUi) -> Unit, + val onSuggestSummary: (NoteItemUi) -> Unit, + val onSuggestTags: (NoteItemUi) -> Unit, +) diff --git a/app/src/main/java/com/itlab/notes/ui/editor/EditorViewModel.kt b/app/src/main/java/com/itlab/notes/ui/editor/EditorViewModel.kt index 4e448ffb..a7dd1644 100644 --- a/app/src/main/java/com/itlab/notes/ui/editor/EditorViewModel.kt +++ b/app/src/main/java/com/itlab/notes/ui/editor/EditorViewModel.kt @@ -9,6 +9,9 @@ class EditorViewModel( initialNote: NoteItemUi, ) { private val noteId: String = initialNote.id + private val folderId: String? = initialNote.folderId + private val tags: Set = initialNote.tags + private val summary: String? = initialNote.summary var title: String by mutableStateOf(initialNote.title) private set @@ -29,5 +32,8 @@ class EditorViewModel( id = noteId, title = title, content = content, + folderId = folderId, + tags = tags, + summary = summary, ) } diff --git a/app/src/main/java/com/itlab/notes/ui/notes/NoteItemUi.kt b/app/src/main/java/com/itlab/notes/ui/notes/NoteItemUi.kt index 618a112f..90721ece 100644 --- a/app/src/main/java/com/itlab/notes/ui/notes/NoteItemUi.kt +++ b/app/src/main/java/com/itlab/notes/ui/notes/NoteItemUi.kt @@ -5,4 +5,6 @@ data class NoteItemUi( val title: String, val content: String, val folderId: String? = null, + val tags: Set = emptySet(), + val summary: String? = null, ) diff --git a/build.gradle.kts b/build.gradle.kts index db206695..bdbcaba5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -103,6 +103,14 @@ subprojects { "com.itlab.notes.ui.notes.*", "com.itlab.notes.ui.theme.*", ) + if (name == "ai") { + classes( + "com.itlab.ai.NativeLlmBridge", + "com.itlab.ai.OpenVinoGenAiBackend", + "com.itlab.ai.OpenVinoNativeRuntime*", + "com.itlab.ai.di.*", + ) + } } } verify { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 734a4656..d2bb7111 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,7 @@ timber = "5.0.1" kotlinxSerializationJson = "1.11.0" robolectric = "4.16.1" coroutinesTest = "1.11.0" +coroutines = "1.11.0" coreTesting = "2.2.0" kotlinxCoroutines = "1.7.3" workManager = "2.9.0" @@ -56,6 +57,7 @@ timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "tim androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" } androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutinesTest" } +kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } androidx-core-testing = { group = "androidx.arch.core", name = "core-testing", version.ref = "coreTesting" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" } androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }