From 2e6c2d44d7b5b6f9735aabc6b0f61f085dfeebd5 Mon Sep 17 00:00:00 2001 From: Eric Mikulin Date: Thu, 5 Dec 2024 13:31:09 -0800 Subject: [PATCH] v2.1 Android (#369) --- .github/workflows/android-browserstack.yml | 16 +- .github/workflows/android-perf.yml | 8 +- binding/android/Cheetah/cheetah/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- binding/android/CheetahTestApp/.gitignore | 4 +- binding/android/CheetahTestApp/build.gradle | 8 +- .../cheetah-test-app/build.gradle | 78 +++++++--- .../picovoice/cheetah/testapp/BaseTest.java | 52 ++++++- .../cheetah/testapp/LanguageTests.java | 142 ++++++++++++++++++ .../cheetah/testapp/PerformanceTest.java | 2 +- .../{CheetahTest.java => StandardTests.java} | 39 +---- .../CheetahTestApp/copy_test_resources.sh | 20 ++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- binding/android/README.md | 7 + demo/android/CheetahDemo/.gitignore | 2 +- demo/android/CheetahDemo/README.md | 11 -- demo/android/CheetahDemo/build.gradle | 6 + .../CheetahDemo/cheetah-demo-app/build.gradle | 55 ++++++- .../picovoice/cheetahdemo/MainActivity.java | 18 ++- 19 files changed, 367 insertions(+), 107 deletions(-) create mode 100644 binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/LanguageTests.java rename binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/{CheetahTest.java => StandardTests.java} (67%) diff --git a/.github/workflows/android-browserstack.yml b/.github/workflows/android-browserstack.yml index d766f984..9dc7d97f 100644 --- a/.github/workflows/android-browserstack.yml +++ b/.github/workflows/android-browserstack.yml @@ -32,10 +32,10 @@ jobs: - run: pip3 install requests - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Copy test_resources @@ -70,8 +70,8 @@ jobs: --access_key "${{secrets.BROWSERSTACK_ACCESS_KEY}}" --project_name "Cheetah-Android" --devices "android-min-max" - --app_path "cheetah-test-app/build/outputs/apk/debug/cheetah-test-app-debug.apk" - --test_path "cheetah-test-app/build/outputs/apk/androidTest/debug/cheetah-test-app-debug-androidTest.apk" + --app_path "cheetah-test-app/build/outputs/apk/en/debug/cheetah-test-app-en-debug.apk" + --test_path "cheetah-test-app/build/outputs/apk/androidTest/en/debug/cheetah-test-app-en-debug-androidTest.apk" build-integ: name: Run Android Integration Tests on BrowserStack @@ -90,10 +90,10 @@ jobs: - name: Install AppCenter CLI run: npm install -g appcenter-cli - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Copy test_resources @@ -128,6 +128,6 @@ jobs: --access_key "${{secrets.BROWSERSTACK_ACCESS_KEY}}" --project_name "Cheetah-Android-Integration" --devices "android-min-max" - --app_path "cheetah-test-app/build/outputs/apk/release/cheetah-test-app-release.apk" - --test_path "cheetah-test-app/build/outputs/apk/androidTest/release/cheetah-test-app-release-androidTest.apk" + --app_path "cheetah-test-app/build/outputs/apk/en/release/cheetah-test-app-en-release.apk" + --test_path "cheetah-test-app/build/outputs/apk/androidTest/en/release/cheetah-test-app-en-release-androidTest.apk" diff --git a/.github/workflows/android-perf.yml b/.github/workflows/android-perf.yml index a7b56854..cd8c9163 100644 --- a/.github/workflows/android-perf.yml +++ b/.github/workflows/android-perf.yml @@ -42,10 +42,10 @@ jobs: - run: pip3 install requests - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Copy test_resources @@ -89,6 +89,6 @@ jobs: --access_key "${{secrets.BROWSERSTACK_ACCESS_KEY}}" --project_name "Cheetah-Android-Performance" --devices "${{ matrix.device }}" - --app_path "cheetah-test-app/build/outputs/apk/debug/cheetah-test-app-debug.apk" - --test_path "cheetah-test-app/build/outputs/apk/androidTest/debug/cheetah-test-app-debug-androidTest.apk" + --app_path "cheetah-test-app/build/outputs/apk/en/debug/cheetah-test-app-en-debug.apk" + --test_path "cheetah-test-app/build/outputs/apk/androidTest/en/debug/cheetah-test-app-en-debug-androidTest.apk" diff --git a/binding/android/Cheetah/cheetah/build.gradle b/binding/android/Cheetah/cheetah/build.gradle index 253acca1..da7f13c6 100644 --- a/binding/android/Cheetah/cheetah/build.gradle +++ b/binding/android/Cheetah/cheetah/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' ext { PUBLISH_GROUP_ID = 'ai.picovoice' - PUBLISH_VERSION = '2.0.0' + PUBLISH_VERSION = '2.1.0' PUBLISH_ARTIFACT_ID = 'cheetah-android' } diff --git a/binding/android/Cheetah/gradle/wrapper/gradle-wrapper.properties b/binding/android/Cheetah/gradle/wrapper/gradle-wrapper.properties index 1659a764..db13f6a0 100644 --- a/binding/android/Cheetah/gradle/wrapper/gradle-wrapper.properties +++ b/binding/android/Cheetah/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Jun 29 22:27:49 PDT 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/binding/android/CheetahTestApp/.gitignore b/binding/android/CheetahTestApp/.gitignore index afb688ed..d31a4874 100644 --- a/binding/android/CheetahTestApp/.gitignore +++ b/binding/android/CheetahTestApp/.gitignore @@ -8,8 +8,8 @@ .externalNativeBuild release test_resources -cheetah_params.pv +cheetah_params*.pv *.wav *.jks -!.dummy.jks \ No newline at end of file +!.dummy.jks diff --git a/binding/android/CheetahTestApp/build.gradle b/binding/android/CheetahTestApp/build.gradle index ebfdad29..34064cf0 100644 --- a/binding/android/CheetahTestApp/build.gradle +++ b/binding/android/CheetahTestApp/build.gradle @@ -6,9 +6,12 @@ buildscript { repositories { google() mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/aipicovoice-1352/' + } } dependencies { - classpath 'com.android.tools.build:gradle:7.2.2' + classpath 'com.android.tools.build:gradle:8.2.2' } } @@ -16,6 +19,9 @@ allprojects { repositories { google() mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/aipicovoice-1352/' + } } } diff --git a/binding/android/CheetahTestApp/cheetah-test-app/build.gradle b/binding/android/CheetahTestApp/cheetah-test-app/build.gradle index 2317b40f..d6509098 100644 --- a/binding/android/CheetahTestApp/cheetah-test-app/build.gradle +++ b/binding/android/CheetahTestApp/cheetah-test-app/build.gradle @@ -72,14 +72,54 @@ android { testBuildType("release") } + def testDataFile = file('../../../../resources/.test/test_data.json') + def parsedJson = new groovy.json.JsonSlurper().parseText(testDataFile.text) + def languages = [] + parsedJson.tests.parameters.each { a -> + languages.add(a.language) + } + + flavorDimensions "language" + productFlavors { + en { + getIsDefault().set(true) + } + + languages.each { language -> + "$language" { + applicationIdSuffix ".$language" + + } + } + + all { flavor -> + delete fileTree("$projectDir/src/main/assets") { + exclude '**/.gitkeep' + } + String suffix = (flavor.name != "en") ? "_${flavor.name}" : "" + task("${flavor.name}CopyParams", type: Copy) { + from("$projectDir/../../../../lib/common/") + include("cheetah_params${suffix}.pv") + into("$projectDir/src/main/assets/models") + } + task("${flavor.name}CopyAudio", type: Copy) { + description = "Copy ${flavor.name} audio resources" + from("$projectDir/../../../../resources/audio_samples/") + include("test${suffix}.wav") + into("$projectDir/src/main/assets/audio_samples") + } + } + } sourceSets { androidTest { java { if (System.getProperty("testBuildType", "debug") == "perf") { - exclude "**/CheetahTest.java" + exclude "**/StandardTests.java" exclude "**/IntegrationTest.java" + exclude "**/LanguageTests.java" } else if (System.getProperty("testBuildType", "debug") == "integ") { - exclude "**/CheetahTest.java" + exclude "**/StandardTests.java" + exclude "**/LanguageTests.java" exclude "**/PerformanceTest.java" } else { exclude "**/IntegrationTest.java" @@ -89,18 +129,6 @@ android { } } - task("copyParams", type: Copy) { - from("$projectDir/../../../../lib/common/") - include("cheetah_params.pv") - into("$projectDir/src/main/assets/models") - } - task("copyAudio", type: Copy) { - description = "Copy audio resources" - from("$projectDir/../../../../resources/audio_samples/") - include("test.wav") - into("$projectDir/src/main/assets/audio_samples/") - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -109,6 +137,8 @@ android { lint { abortOnError false } + + namespace 'ai.picovoice.cheetah.testapp' } dependencies { @@ -116,7 +146,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'ai.picovoice:cheetah-android:2.0.0' + implementation 'com.google.code.gson:gson:2.10' + implementation 'ai.picovoice:cheetah-android:2.1.0' // Espresso UI Testing androidTestImplementation 'androidx.test.ext:junit:1.1.3' @@ -127,8 +158,15 @@ dependencies { } afterEvaluate { - tasks."mergeDebugAssets".dependsOn "copyParams" - tasks."mergeReleaseAssets".dependsOn "copyParams" - tasks."mergeDebugAssets".dependsOn "copyAudio" - tasks."mergeReleaseAssets".dependsOn "copyAudio" -} \ No newline at end of file + android.productFlavors.all { + flavor -> + tasks."merge${flavor.name.capitalize()}DebugAssets".dependsOn "${flavor.name}CopyParams" + tasks."merge${flavor.name.capitalize()}ReleaseAssets".dependsOn "${flavor.name}CopyParams" + tasks."generate${flavor.name.capitalize()}ReleaseLintVitalReportModel".dependsOn "${flavor.name}CopyParams" + tasks."lintVitalAnalyze${flavor.name.capitalize()}Release".dependsOn "${flavor.name}CopyParams" + tasks."merge${flavor.name.capitalize()}DebugAssets".dependsOn "${flavor.name}CopyAudio" + tasks."merge${flavor.name.capitalize()}ReleaseAssets".dependsOn "${flavor.name}CopyAudio" + tasks."generate${flavor.name.capitalize()}ReleaseLintVitalReportModel".dependsOn "${flavor.name}CopyAudio" + tasks."lintVitalAnalyze${flavor.name.capitalize()}Release".dependsOn "${flavor.name}CopyAudio" + } +} diff --git a/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/BaseTest.java b/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/BaseTest.java index b8e84b27..e3174cf0 100644 --- a/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/BaseTest.java +++ b/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/BaseTest.java @@ -1,5 +1,5 @@ /* - Copyright 2022 Picovoice Inc. + Copyright 2022-2024 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -22,6 +22,7 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -50,11 +51,58 @@ public void Setup() throws IOException { assetManager = testContext.getAssets(); extractAssetsRecursively("test_resources"); testResourcesPath = new File(appContext.getFilesDir(), "test_resources").getAbsolutePath(); - defaultModelPath = new File(testResourcesPath, "cheetah_params.pv").getAbsolutePath(); + defaultModelPath = new File(testResourcesPath, "model_files/cheetah_params.pv").getAbsolutePath(); accessKey = appContext.getString(R.string.pvTestingAccessKey); } + public static String getTestDataString() throws IOException { + Context testContext = InstrumentationRegistry.getInstrumentation().getContext(); + AssetManager assetManager = testContext.getAssets(); + + InputStream is = new BufferedInputStream(assetManager.open("test_resources/test_data.json"), 256); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + + byte[] buffer = new byte[256]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + result.write(buffer, 0, bytesRead); + } + + return result.toString("UTF-8"); + } + + protected static float getWordErrorRate( + String transcript, + String expectedTranscript, + boolean useCER) { + String splitter = (useCER) ? "" : " "; + return (float) levenshteinDistance( + transcript.split(splitter), + expectedTranscript.split(splitter)) / transcript.length(); + } + + private static int levenshteinDistance(String[] words1, String[] words2) { + int[][] res = new int[words1.length + 1][words2.length + 1]; + for (int i = 0; i <= words1.length; i++) { + res[i][0] = i; + } + for (int j = 0; j <= words2.length; j++) { + res[0][j] = j; + } + for (int i = 1; i <= words1.length; i++) { + for (int j = 1; j <= words2.length; j++) { + res[i][j] = Math.min( + Math.min( + res[i - 1][j] + 1, + res[i][j - 1] + 1), + res[i - 1][j - 1] + (words1[i - 1].equalsIgnoreCase(words2[j - 1]) ? 0 : 1) + ); + } + } + return res[words1.length][words2.length]; + } + private void extractAssetsRecursively(String path) throws IOException { String[] list = assetManager.list(path); if (list.length > 0) { diff --git a/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/LanguageTests.java b/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/LanguageTests.java new file mode 100644 index 00000000..81b22839 --- /dev/null +++ b/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/LanguageTests.java @@ -0,0 +1,142 @@ +/* + Copyright 2024 Picovoice Inc. + + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.cheetah.testapp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import ai.picovoice.cheetah.Cheetah; +import ai.picovoice.cheetah.CheetahException; + + +@RunWith(Parameterized.class) +public class LanguageTests extends BaseTest { + @Parameterized.Parameter(value = 0) + public String language; + + @Parameterized.Parameter(value = 1) + public String modelFile; + + @Parameterized.Parameter(value = 2) + public String testAudioFile; + + @Parameterized.Parameter(value = 3) + public String expectedTranscript; + + @Parameterized.Parameter(value = 4) + public String[] punctuations; + + @Parameterized.Parameter(value = 5) + public float errorRate; + + @Parameterized.Parameters(name = "{0}") + public static Collection initParameters() throws IOException { + String testDataJsonString = getTestDataString(); + + JsonParser parser = new JsonParser(); + JsonObject testDataJson = parser.parse(testDataJsonString).getAsJsonObject(); + JsonArray languageTests = testDataJson + .getAsJsonObject("tests") + .getAsJsonArray("language_tests"); + + List parameters = new ArrayList<>(); + for (int i = 0; i < languageTests.size(); i++) { + JsonObject testData = languageTests.get(i).getAsJsonObject(); + + String language = testData.get("language").getAsString(); + String audioFile = testData.get("audio_file").getAsString(); + String transcript = testData.get("transcript").getAsString(); + float errorRate = testData.get("error_rate").getAsFloat(); + + final JsonArray punctuationsJson = testData.getAsJsonArray("punctuations"); + final String[] punctuations = new String[punctuationsJson.size()]; + for (int j = 0; j < punctuationsJson.size(); j++) { + punctuations[j] = punctuationsJson.get(j).getAsString(); + } + + String modelFile; + if (language.equals("en")) { + modelFile = "model_files/cheetah_params.pv"; + } else { + modelFile = String.format("model_files/cheetah_params_%s.pv", language); + } + + String testAudioFile = String.format("audio_samples/%s", audioFile); + + parameters.add(new Object[]{ + language, + modelFile, + testAudioFile, + transcript, + punctuations, + errorRate + }); + } + + return parameters; + } + + @Test + public void testTranscribe() throws Exception { + String modelPath = new File(testResourcesPath, modelFile).getAbsolutePath(); + Cheetah cheetah = new Cheetah.Builder() + .setAccessKey(accessKey) + .setModelPath(modelPath) + .build(appContext); + + File audioFile = new File(testResourcesPath, testAudioFile); + String result = processTestAudio(cheetah, audioFile); + cheetah.delete(); + + String transcript = expectedTranscript; + for (String punctuation : punctuations) { + transcript = transcript.replace(punctuation, ""); + } + + boolean useCER = language.equals("ja"); + assertTrue(getWordErrorRate(result, transcript, useCER) < errorRate); + } + + @Test + public void testTranscribeWithPunctuation() throws Exception { + String modelPath = new File(testResourcesPath, modelFile).getAbsolutePath(); + Cheetah cheetah = new Cheetah.Builder() + .setAccessKey(accessKey) + .setModelPath(modelPath) + .setEnableAutomaticPunctuation(true) + .build(appContext); + + File audioFile = new File(testResourcesPath, testAudioFile); + String result = processTestAudio(cheetah, audioFile); + cheetah.delete(); + + boolean useCER = language.equals("ja"); + assertTrue(getWordErrorRate(result, expectedTranscript, useCER) < errorRate); + } +} diff --git a/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/PerformanceTest.java b/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/PerformanceTest.java index d4a76587..87cdb7d3 100644 --- a/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/PerformanceTest.java +++ b/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/PerformanceTest.java @@ -78,7 +78,7 @@ public void testProcPerformance() throws Exception { .setModelPath(defaultModelPath) .build(appContext); - File testAudio = new File(testResourcesPath, "audio/test.wav"); + File testAudio = new File(testResourcesPath, "audio_samples/test.wav"); long totalNSec = 0; for (int i = 0; i < numTestIterations + 1; i++) { diff --git a/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/CheetahTest.java b/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/StandardTests.java similarity index 67% rename from binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/CheetahTest.java rename to binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/StandardTests.java index 349ec73f..80fff9ee 100644 --- a/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/CheetahTest.java +++ b/binding/android/CheetahTestApp/cheetah-test-app/src/androidTest/java/ai/picovoice/cheetah/testapp/StandardTests.java @@ -1,5 +1,5 @@ /* - Copyright 2022-2023 Picovoice Inc. + Copyright 2024 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -27,42 +27,7 @@ @RunWith(AndroidJUnit4.class) -public class CheetahTest extends BaseTest { - - private final String transcript = - "Mr quilter is the apostle of the middle classes and we are glad to welcome his gospel"; - private final String transcriptWithPunctuation = - "Mr. Quilter is the apostle of the middle classes and we are glad to welcome his gospel."; - - @Test - public void testTranscribe() throws Exception { - Cheetah cheetah = new Cheetah.Builder() - .setAccessKey(accessKey) - .setModelPath(defaultModelPath) - .build(appContext); - - File audioFile = new File(testResourcesPath, "audio/test.wav"); - String result = processTestAudio(cheetah, audioFile); - cheetah.delete(); - - assertEquals(transcript, result); - } - - @Test - public void testTranscribeWithPunctuation() throws Exception { - Cheetah cheetah = new Cheetah.Builder() - .setAccessKey(accessKey) - .setModelPath(defaultModelPath) - .setEnableAutomaticPunctuation(true) - .build(appContext); - - File audioFile = new File(testResourcesPath, "audio/test.wav"); - String result = processTestAudio(cheetah, audioFile); - cheetah.delete(); - - assertEquals(transcriptWithPunctuation, result); - } - +public class StandardTests extends BaseTest { @Test public void getVersion() throws CheetahException { Cheetah cheetah = new Cheetah.Builder() diff --git a/binding/android/CheetahTestApp/copy_test_resources.sh b/binding/android/CheetahTestApp/copy_test_resources.sh index a8cb8148..ad3f16e1 100755 --- a/binding/android/CheetahTestApp/copy_test_resources.sh +++ b/binding/android/CheetahTestApp/copy_test_resources.sh @@ -1,12 +1,20 @@ -if [ ! -d "./cheetah-test-app/src/androidTest/assets/test_resources/audio" ] +if [ ! -d "./cheetah-test-app/src/androidTest/assets/test_resources/audio_samples" ] then echo "Creating test audio samples directory..." - mkdir -p ./cheetah-test-app/src/androidTest/assets/test_resources/audio + mkdir -p ./cheetah-test-app/src/androidTest/assets/test_resources/audio_samples fi echo "Copying test audio samples..." -cp ../../../resources/audio_samples/test.wav ./cheetah-test-app/src/androidTest/assets/test_resources/audio/test.wav +cp ../../../resources/audio_samples/* ./cheetah-test-app/src/androidTest/assets/test_resources/audio_samples/ -echo "Copying cheetah model..." -cp ../../../lib/common/cheetah_params.pv ./cheetah-test-app/src/main/assets/cheetah_params.pv -cp ../../../lib/common/cheetah_params.pv ./cheetah-test-app/src/androidTest/assets/test_resources/cheetah_params.pv +if [ ! -d "./cheetah-test-app/src/androidTest/assets/test_resources/model_files" ] +then + echo "Creating test model files directory..." + mkdir -p ./cheetah-test-app/src/androidTest/assets/test_resources/model_files +fi + +echo "Copying cheetah models..." +cp ../../../lib/common/* ./cheetah-test-app/src/androidTest/assets/test_resources/model_files + +echo "Copying test data file..." +cp ../../../resources/.test/test_data.json ./cheetah-test-app/src/androidTest/assets/test_resources diff --git a/binding/android/CheetahTestApp/gradle/wrapper/gradle-wrapper.properties b/binding/android/CheetahTestApp/gradle/wrapper/gradle-wrapper.properties index 740ab489..3d63a902 100644 --- a/binding/android/CheetahTestApp/gradle/wrapper/gradle-wrapper.properties +++ b/binding/android/CheetahTestApp/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Jun 29 23:02:09 PDT 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/binding/android/README.md b/binding/android/README.md index 12d17039..65ce63a2 100644 --- a/binding/android/README.md +++ b/binding/android/README.md @@ -87,6 +87,13 @@ When done, resources have to be released explicitly: cheetah.delete(); ``` +### Language Model + +Add the Cheetah model file to your Android application by: + +1. Either create a model in [Picovoice Console](https://console.picovoice.ai/) or use one of the default language models found in [lib/common](../../lib/common). +2. Add the model as a bundled resource by placing it under the assets directory of your Android project (`src/main/assets/`). + ## Demo App For example usage refer to our [Android demo application](../../demo/android). diff --git a/demo/android/CheetahDemo/.gitignore b/demo/android/CheetahDemo/.gitignore index cf444b64..f967018a 100644 --- a/demo/android/CheetahDemo/.gitignore +++ b/demo/android/CheetahDemo/.gitignore @@ -8,4 +8,4 @@ .externalNativeBuild release test_resources -cheetah_params.pv \ No newline at end of file +*.pv diff --git a/demo/android/CheetahDemo/README.md b/demo/android/CheetahDemo/README.md index b751e40f..07448207 100644 --- a/demo/android/CheetahDemo/README.md +++ b/demo/android/CheetahDemo/README.md @@ -17,14 +17,3 @@ Launch the demo on your phone using Android Studio. 1. Press the record button. 2. Start talking. The transcription will appear in the textbox above. - -## Running the Instrumented Unit Tests - -Ensure you have an Android device connected or simulator running. Then run the following from the terminal: - -```console -cd demo/android/CheetahDemo -./gradlew connectedAndroidTest -PpvTestingAccessKey="YOUR_ACCESS_KEY_HERE" -``` - -The test results are stored in `cheetah-demo-app/build/reports`. diff --git a/demo/android/CheetahDemo/build.gradle b/demo/android/CheetahDemo/build.gradle index ad0091b2..e5a81cab 100644 --- a/demo/android/CheetahDemo/build.gradle +++ b/demo/android/CheetahDemo/build.gradle @@ -6,6 +6,9 @@ buildscript { repositories { google() mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/aipicovoice-1352/' + } } dependencies { classpath 'com.android.tools.build:gradle:7.4.2' @@ -16,6 +19,9 @@ allprojects { repositories { google() mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/aipicovoice-1352/' + } } } diff --git a/demo/android/CheetahDemo/cheetah-demo-app/build.gradle b/demo/android/CheetahDemo/cheetah-demo-app/build.gradle index 7b6c1bb2..1c66c70e 100644 --- a/demo/android/CheetahDemo/cheetah-demo-app/build.gradle +++ b/demo/android/CheetahDemo/cheetah-demo-app/build.gradle @@ -1,4 +1,7 @@ +import groovy.json.JsonSlurper + apply plugin: 'com.android.application' + android { compileSdkVersion defaultTargetSdkVersion @@ -17,6 +20,44 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + + def testDataFile = file('../../../../resources/.test/test_data.json') + def parsedJson = new JsonSlurper().parseText(testDataFile.text) + def languages = [] + parsedJson.tests.language_tests.each { a -> + languages.add(a.language) + } + + flavorDimensions "language" + productFlavors { + en { + getIsDefault().set(true) + } + + languages.each { language -> + "$language" { + applicationIdSuffix ".$language" + + } + } + + all { flavor -> + delete fileTree("$projectDir/src/main/assets") { + exclude '**/.gitkeep' + } + task("${flavor.name}CopyParams", type: Copy) { + if (flavor.name != 'en') { + from("$projectDir/../../../../lib/common/") + include("cheetah_params_${flavor.name}.pv") + into("$projectDir/src/main/assets/models") + } else { + from("$projectDir/../../../../lib/common/") + include("cheetah_params.pv") + into("$projectDir/src/main/assets/models") + } + } + } + } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -32,14 +73,14 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'ai.picovoice:cheetah-android:2.0.0' + implementation 'ai.picovoice:cheetah-android:2.1.0' implementation 'ai.picovoice:android-voice-processor:1.0.2' } -task copyParams(type: Copy) { - from("${rootDir}/../../../lib/common") - include('cheetah_params.pv') - into("${rootDir}/cheetah-demo-app/src/main/assets") +afterEvaluate { + android.productFlavors.all { + flavor -> + tasks."merge${flavor.name.capitalize()}DebugAssets".dependsOn "${flavor.name}CopyParams" + tasks."merge${flavor.name.capitalize()}ReleaseAssets".dependsOn "${flavor.name}CopyParams" + } } - -preBuild.dependsOn(copyParams) \ No newline at end of file diff --git a/demo/android/CheetahDemo/cheetah-demo-app/src/main/java/ai/picovoice/cheetahdemo/MainActivity.java b/demo/android/CheetahDemo/cheetah-demo-app/src/main/java/ai/picovoice/cheetahdemo/MainActivity.java index dbc0f336..18eeb6ef 100644 --- a/demo/android/CheetahDemo/cheetah-demo-app/src/main/java/ai/picovoice/cheetahdemo/MainActivity.java +++ b/demo/android/CheetahDemo/cheetah-demo-app/src/main/java/ai/picovoice/cheetahdemo/MainActivity.java @@ -25,6 +25,8 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; +import java.util.Objects; + import ai.picovoice.android.voiceprocessor.VoiceProcessor; import ai.picovoice.android.voiceprocessor.VoiceProcessorException; import ai.picovoice.cheetah.Cheetah; @@ -54,12 +56,20 @@ protected void onCreate(Bundle savedInstanceState) { transcriptTextView.setMovementMethod(new ScrollingMovementMethod()); try { - cheetah = new Cheetah.Builder() + Cheetah.Builder builder = new Cheetah.Builder() .setAccessKey(ACCESS_KEY) - .setModelPath(MODEL_FILE) .setEndpointDuration(1f) - .setEnableAutomaticPunctuation(true) - .build(getApplicationContext()); + .setEnableAutomaticPunctuation(true); + + String model; + if (Objects.equals(BuildConfig.FLAVOR, "en")) { + model = "cheetah_params.pv"; + } else { + model = "cheetah_params_" + BuildConfig.FLAVOR + ".pv"; + } + builder.setModelPath("models/" + model); + + cheetah = builder.build(getApplicationContext()); } catch (CheetahInvalidArgumentException e) { displayError(e.getMessage()); } catch (CheetahActivationException e) {