Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e73d52d221 | |||
| 7ff235fb3f | |||
| bef71f8d00 | |||
| 06b98f107e | |||
| 65380da39a | |||
| bb17c0651e | |||
| 94196692c8 |
1
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +1,6 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<state>
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
</state>
|
</state>
|
||||||
</component>
|
</component>
|
||||||
29
.idea/deploymentTargetSelector (SFConflict ispatel 2025-03-03-12-04-56).xml
generated
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="wear">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2025-03-03T16:57:28.073351600Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=RFAX803LMFR" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="mobile">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2025-03-03T16:57:37.023647400Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=569cc5b0" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
15
.idea/deploymentTargetSelector.xml
generated
@@ -2,28 +2,31 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="deploymentTargetSelector">
|
<component name="deploymentTargetSelector">
|
||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="wear">
|
<SelectionState runConfigName="mobile">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2024-09-10T00:57:13.348042Z">
|
<DropdownSelection timestamp="2025-03-09T15:35:16.375287800Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/isp/.android/avd/Wear_OS_Large_Round_API_34.avd" />
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=RFAX803LMFR" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
<DialogSelection />
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
<SelectionState runConfigName="mobile">
|
<SelectionState runConfigName="wear">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2024-09-10T01:00:38.270417Z">
|
<DropdownSelection timestamp="2025-03-29T14:49:45.062493500Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/isp/.android/avd/Wear_OS_Large_Round_API_34.avd" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\thebears\.android\avd\Wear_OS_Large_Round_API_34.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
<DialogSelection />
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="MainActivity">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
2
.idea/kotlinc.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="2.0.20" />
|
<option name="version" value="2.1.10" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
1
.idea/misc.xml
generated
@@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
|||||||
260
.idea/workspace (SFConflict ispatel 2025-03-03-12-04-56).xml
generated
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidLayouts">
|
||||||
|
<shared>
|
||||||
|
<config />
|
||||||
|
</shared>
|
||||||
|
</component>
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="NONE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="e1c82fb5-3f9e-43af-a0be-9f1954a6c1b9" name="Changes" comment="">
|
||||||
|
<change afterPath="$PROJECT_DIR$/mobile/src/main/ic_launcher-playstore.png" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/codeStyles/codeStyleConfig.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/codeStyles/codeStyleConfig.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/deploymentTargetSelector.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/deploymentTargetSelector.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/gradle/libs.versions.toml" beforeDir="false" afterPath="$PROJECT_DIR$/gradle/libs.versions.toml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/build.gradle.kts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/Downloader.java" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/Downloader.java" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/drawable/ic_launcher_background.xml" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/drawable/ic_launcher_background.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-anydpi/ic_launcher.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-anydpi/ic_launcher_round.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-hdpi/ic_launcher.webp" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-hdpi/ic_launcher.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-hdpi/ic_launcher_round.webp" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-hdpi/ic_launcher_round.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-mdpi/ic_launcher.webp" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-mdpi/ic_launcher.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-mdpi/ic_launcher_round.webp" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-mdpi/ic_launcher_round.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xhdpi/ic_launcher.webp" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xhdpi/ic_launcher.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xhdpi/ic_launcher_round.webp" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xhdpi/ic_launcher_round.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.webp" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.webp" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/wear/src/main/res/mipmap-hdpi/ic_launcher.webp" beforeDir="false" afterPath="$PROJECT_DIR$/wear/src/main/res/mipmap-hdpi/ic_launcher.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/wear/src/main/res/mipmap-mdpi/ic_launcher.webp" beforeDir="false" afterPath="$PROJECT_DIR$/wear/src/main/res/mipmap-mdpi/ic_launcher.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/wear/src/main/res/mipmap-xhdpi/ic_launcher.webp" beforeDir="false" afterPath="$PROJECT_DIR$/wear/src/main/res/mipmap-xhdpi/ic_launcher.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/wear/src/main/res/mipmap-xxhdpi/ic_launcher.webp" beforeDir="false" afterPath="$PROJECT_DIR$/wear/src/main/res/mipmap-xxhdpi/ic_launcher.webp" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/wear/src/main/res/mipmap-xxxhdpi/ic_launcher.webp" beforeDir="false" afterPath="$PROJECT_DIR$/wear/src/main/res/mipmap-xxxhdpi/ic_launcher.webp" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ClangdSettings">
|
||||||
|
<option name="formatViaClangd" value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[DeviceId(pluginId=PhysicalDevice, isTemplate=false, identifier=serial=569cc5b0)]" />
|
||||||
|
<component name="ExternalProjectsData">
|
||||||
|
<projectState path="$PROJECT_DIR$">
|
||||||
|
<ProjectState />
|
||||||
|
</projectState>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 0
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2toSwb3riPZMH7ufWxT9aBYCKaO" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"Android App.mobile.executor": "Run",
|
||||||
|
"Android App.wear.executor": "Run",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
|
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||||
|
"cf.first.check.clang-format": "false",
|
||||||
|
"cidr.known.project.marker": "true",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
|
"kotlin-language-version-configured": "true",
|
||||||
|
"last_opened_file_path": "C:/Users/isp/Seafile/Designs/Android/bird_sound_identify_wearos",
|
||||||
|
"project.structure.last.edited": "Modules",
|
||||||
|
"project.structure.proportion": "0.17",
|
||||||
|
"project.structure.side.proportion": "0.2"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RunManager" selected="Android App.mobile">
|
||||||
|
<configuration name="mobile" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||||
|
<module name="identify.mobile" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
<configuration name="wear" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||||
|
<module name="identify.wear" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="e1c82fb5-3f9e-43af-a0be-9f1954a6c1b9" name="Changes" comment="" />
|
||||||
|
<created>1741017174616</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1741017174616</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="play_dynamic_filters_status">
|
||||||
|
<option name="appIdToCheckInfo">
|
||||||
|
<map>
|
||||||
|
<entry key="com.birdsounds.identify">
|
||||||
|
<value>
|
||||||
|
<CheckInfo lastCheckTimestamp="1741019799252" />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="com.birdsounds.identify.test">
|
||||||
|
<value>
|
||||||
|
<CheckInfo lastCheckTimestamp="1741019799252" />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
254
.idea/workspace (SFConflict ispatel 2025-03-10-10-03-26).xml
generated
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidLayouts">
|
||||||
|
<shared>
|
||||||
|
<config />
|
||||||
|
</shared>
|
||||||
|
<layouts>
|
||||||
|
<layout url="file://$PROJECT_DIR$/wear/src/main/res/layout/layout.xml">
|
||||||
|
<config>
|
||||||
|
<theme>@android:style/Theme.DeviceDefault</theme>
|
||||||
|
</config>
|
||||||
|
</layout>
|
||||||
|
</layouts>
|
||||||
|
</component>
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="NONE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="e1c82fb5-3f9e-43af-a0be-9f1954a6c1b9" name="Changes" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/Downloader.java" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/Downloader.java" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ClangdSettings">
|
||||||
|
<option name="formatViaClangd" value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[]" />
|
||||||
|
<component name="ExternalProjectsData">
|
||||||
|
<projectState path="$PROJECT_DIR$">
|
||||||
|
<ProjectState />
|
||||||
|
</projectState>
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Class" />
|
||||||
|
<option value="Kotlin Class" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 0
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2toSwb3riPZMH7ufWxT9aBYCKaO" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">{
|
||||||
|
"keyToString": {
|
||||||
|
"Android App.mobile.executor": "Run",
|
||||||
|
"Android App.wear.executor": "Run",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
|
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||||
|
"cf.first.check.clang-format": "false",
|
||||||
|
"cidr.known.project.marker": "true",
|
||||||
|
"com.google.services.firebase.aqiPopupShown": "true",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
|
"kotlin-language-version-configured": "true",
|
||||||
|
"last_opened_file_path": "C:/Users/isp/Seafile/Designs/Android/bird_sound_identify_wearos",
|
||||||
|
"project.structure.last.edited": "Modules",
|
||||||
|
"project.structure.proportion": "0.17",
|
||||||
|
"project.structure.side.proportion": "0.2"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\Users\isp\Seafile\Designs\Android\bird_sound_identify_wearos\mobile\src\main\assets\2024_08_16" />
|
||||||
|
</key>
|
||||||
|
<key name="MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY">
|
||||||
|
<recent name="com.birdsounds.identify.presentation" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager" selected="Android App.mobile">
|
||||||
|
<configuration name="mobile" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||||
|
<module name="identify.mobile" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
<configuration name="wear" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||||
|
<module name="identify.wear" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="e1c82fb5-3f9e-43af-a0be-9f1954a6c1b9" name="Changes" comment="" />
|
||||||
|
<created>1741017174616</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1741017174616</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="play_dynamic_filters_status">
|
||||||
|
<option name="appIdToCheckInfo">
|
||||||
|
<map>
|
||||||
|
<entry key="com.birdsounds.identify">
|
||||||
|
<value>
|
||||||
|
<CheckInfo lastCheckTimestamp="1741381679027" />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="com.birdsounds.identify.test">
|
||||||
|
<value>
|
||||||
|
<CheckInfo lastCheckTimestamp="1741381679027" />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
254
.idea/workspace (SFConflict ispatel@live.com 2025-03-08-01-11-41).xml
generated
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidLayouts">
|
||||||
|
<shared>
|
||||||
|
<config />
|
||||||
|
</shared>
|
||||||
|
<layouts>
|
||||||
|
<layout url="file://$PROJECT_DIR$/wear/src/main/res/layout/layout.xml">
|
||||||
|
<config>
|
||||||
|
<theme>@android:style/Theme.DeviceDefault</theme>
|
||||||
|
</config>
|
||||||
|
</layout>
|
||||||
|
</layouts>
|
||||||
|
</component>
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="NONE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="e1c82fb5-3f9e-43af-a0be-9f1954a6c1b9" name="Changes" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/Downloader.java" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/Downloader.java" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ClangdSettings">
|
||||||
|
<option name="formatViaClangd" value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[]" />
|
||||||
|
<component name="ExternalProjectsData">
|
||||||
|
<projectState path="$PROJECT_DIR$">
|
||||||
|
<ProjectState />
|
||||||
|
</projectState>
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Class" />
|
||||||
|
<option value="Kotlin Class" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 0
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2toSwb3riPZMH7ufWxT9aBYCKaO" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">{
|
||||||
|
"keyToString": {
|
||||||
|
"Android App.mobile.executor": "Run",
|
||||||
|
"Android App.wear.executor": "Run",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
|
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||||
|
"cf.first.check.clang-format": "false",
|
||||||
|
"cidr.known.project.marker": "true",
|
||||||
|
"com.google.services.firebase.aqiPopupShown": "true",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
|
"kotlin-language-version-configured": "true",
|
||||||
|
"last_opened_file_path": "C:/Users/isp/Seafile/Designs/Android/bird_sound_identify_wearos",
|
||||||
|
"project.structure.last.edited": "Modules",
|
||||||
|
"project.structure.proportion": "0.17",
|
||||||
|
"project.structure.side.proportion": "0.2"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\Users\isp\Seafile\Designs\Android\bird_sound_identify_wearos\mobile\src\main\assets\2024_08_16" />
|
||||||
|
</key>
|
||||||
|
<key name="MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY">
|
||||||
|
<recent name="com.birdsounds.identify.presentation" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager" selected="Android App.mobile">
|
||||||
|
<configuration name="mobile" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||||
|
<module name="identify.mobile" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
<configuration name="wear" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||||
|
<module name="identify.wear" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="e1c82fb5-3f9e-43af-a0be-9f1954a6c1b9" name="Changes" comment="" />
|
||||||
|
<created>1741017174616</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1741017174616</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="play_dynamic_filters_status">
|
||||||
|
<option name="appIdToCheckInfo">
|
||||||
|
<map>
|
||||||
|
<entry key="com.birdsounds.identify">
|
||||||
|
<value>
|
||||||
|
<CheckInfo lastCheckTimestamp="1741381679027" />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="com.birdsounds.identify.test">
|
||||||
|
<value>
|
||||||
|
<CheckInfo lastCheckTimestamp="1741381679027" />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
kotlin version: 2.0.20
|
|
||||||
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
|
||||||
1. Kotlin compile daemon is ready
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.8.0"
|
accompanistFlowlayout = "0.28.0"
|
||||||
|
agp = "8.8.1"
|
||||||
|
composeNavigationVersion = "1.3.1"
|
||||||
|
horologistComposeTools = "0.6.18"
|
||||||
|
horologistAudio = "0.6.18"
|
||||||
|
horologistMediaData = "0.6.8"
|
||||||
|
horologistMediaUi = "0.6.8"
|
||||||
kotlin = "2.0.0"
|
kotlin = "2.0.0"
|
||||||
coreKtx = "1.13.1"
|
coreKtx = "1.13.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.1.5"
|
junitVersion = "1.1.5"
|
||||||
espressoCore = "3.5.1"
|
espressoCore = "3.5.1"
|
||||||
appcompat = "1.6.1"
|
appcompat = "1.6.1"
|
||||||
|
kotlinxCoroutinesPlayServices = "1.8.1"
|
||||||
|
lifecycleViewmodelCompose = "2.8.4"
|
||||||
|
materialIconsCore = "1.6.8"
|
||||||
|
materialIconsExtended = "1.7.8"
|
||||||
|
media3Exoplayer = "1.4.0"
|
||||||
|
navigationCompose = "2.8.0-rc01"
|
||||||
playServicesWearable = "18.2.0"
|
playServicesWearable = "18.2.0"
|
||||||
material = "1.10.0"
|
material = "1.10.0"
|
||||||
activity = "1.9.1"
|
activity = "1.9.1"
|
||||||
@@ -13,6 +25,9 @@ constraintlayout = "2.1.4"
|
|||||||
composeBom = "2024.04.01"
|
composeBom = "2024.04.01"
|
||||||
composeMaterial = "1.2.1"
|
composeMaterial = "1.2.1"
|
||||||
composeFoundation = "1.2.1"
|
composeFoundation = "1.2.1"
|
||||||
|
statelyConcurrentCollections = "2.0.0"
|
||||||
|
uiTooling = "1.3.1"
|
||||||
|
wearOngoing = "1.0.0"
|
||||||
wearToolingPreview = "1.0.0"
|
wearToolingPreview = "1.0.0"
|
||||||
activityCompose = "1.9.1"
|
activityCompose = "1.9.1"
|
||||||
coreSplashscreen = "1.0.1"
|
coreSplashscreen = "1.0.1"
|
||||||
@@ -21,14 +36,33 @@ media3Common = "1.4.0"
|
|||||||
composeMaterial3 = "1.0.0-alpha23"
|
composeMaterial3 = "1.0.0-alpha23"
|
||||||
workRuntimeKtx = "2.9.1"
|
workRuntimeKtx = "2.9.1"
|
||||||
lifecycleRuntimeKtx = "2.6.1"
|
lifecycleRuntimeKtx = "2.6.1"
|
||||||
litert = "1.0.1"
|
runtimeAndroid = "1.6.6"
|
||||||
|
datastoreCoreAndroid = "1.1.3"
|
||||||
|
coreKtxVersion = "1.13.0"
|
||||||
|
animationCoreAndroid = "1.6.6"
|
||||||
|
#litert = "1.0.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanistFlowlayout" }
|
||||||
|
androidx-compose-navigation-v131 = { module = "androidx.wear.compose:compose-navigation", version.ref = "composeNavigationVersion" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
|
||||||
|
androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIconsCore" }
|
||||||
|
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
|
||||||
|
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" }
|
||||||
|
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
||||||
|
androidx-wear-ongoing = { module = "androidx.wear:wear-ongoing", version.ref = "wearOngoing" }
|
||||||
|
horologist-audio-ui = { module = "com.google.android.horologist:horologist-audio-ui", version.ref = "horologistComposeTools" }
|
||||||
|
horologist-audio = { module = "com.google.android.horologist:horologist-audio", version.ref = "horologistAudio" }
|
||||||
|
horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "horologistMediaData" }
|
||||||
|
horologist-compose-tools = { module = "com.google.android.horologist:horologist-compose-tools", version.ref = "horologistComposeTools" }
|
||||||
|
horologist-media-ui = { module = "com.google.android.horologist:horologist-media-ui", version.ref = "horologistMediaUi" }
|
||||||
|
horologist-media-data = { module = "com.google.android.horologist:horologist-media-data", version.ref = "horologistMediaData" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
|
kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" }
|
||||||
play-services-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "playServicesWearable" }
|
play-services-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "playServicesWearable" }
|
||||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
||||||
@@ -51,10 +85,16 @@ androidx-compose-material3 = { group = "androidx.wear.compose", name = "compose-
|
|||||||
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }
|
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }
|
||||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "litert" }
|
androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" }
|
||||||
|
androidx-datastore-core-android = { group = "androidx.datastore", name = "datastore-core-android", version.ref = "datastoreCoreAndroid" }
|
||||||
|
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtxVersion" }
|
||||||
|
stately-concurrent-collections = { module = "co.touchlab:stately-concurrent-collections", version.ref = "statelyConcurrentCollections" }
|
||||||
|
ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "uiTooling" }
|
||||||
|
androidx-animation-core-android = { group = "androidx.compose.animation", name = "animation-core-android", version.ref = "animationCoreAndroid" }
|
||||||
|
#litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "litert" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.0.20" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.1.10" }
|
||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.compose)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.birdsounds.identify"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.birdsounds.identify"
|
||||||
|
minSdk = 34
|
||||||
|
targetSdk = 34
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.appcompat)
|
||||||
|
implementation(libs.play.services.wearable)
|
||||||
|
implementation(libs.material)
|
||||||
|
implementation(libs.androidx.activity)
|
||||||
|
implementation(libs.androidx.constraintlayout)
|
||||||
|
implementation(libs.androidx.work.runtime.ktx)
|
||||||
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
|
implementation(libs.androidx.ui)
|
||||||
|
implementation("uk.me.berndporr:iirj:1.7")
|
||||||
|
implementation(libs.androidx.ui.graphics)
|
||||||
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
|
implementation(libs.androidx.material3)
|
||||||
|
implementation(libs.litert)
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
|
implementation("org.tensorflow:tensorflow-lite-gpu:2.12.0")
|
||||||
|
implementation("org.tensorflow:tensorflow-lite-support:0.4.4")
|
||||||
|
implementation("org.tensorflow:tensorflow-lite-task-vision:0.4.4")
|
||||||
|
implementation("org.tensorflow:tensorflow-lite-task-text:0.4.4")
|
||||||
|
implementation("com.google.android.gms:play-services-tflite-gpu:16.2.0")
|
||||||
|
implementation("com.google.android.gms:play-services-tflite-java:16.0.0-beta01")
|
||||||
|
wearApp(project(":wear"))
|
||||||
|
}
|
||||||
@@ -40,6 +40,10 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api(fileTree("libs") {
|
||||||
|
include("*.jar")
|
||||||
|
})
|
||||||
|
api(files("libs/opus.aar"))
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.play.services.wearable)
|
implementation(libs.play.services.wearable)
|
||||||
@@ -51,11 +55,17 @@ dependencies {
|
|||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
|
|
||||||
|
implementation("androidx.datastore:datastore-preferences:1.1.3")
|
||||||
|
implementation("org.tensorflow:tensorflow-lite:2.6.0")
|
||||||
|
// implementation("com.google.android.gms:play-services-tflite:20.0.0")
|
||||||
implementation("uk.me.berndporr:iirj:1.7")
|
implementation("uk.me.berndporr:iirj:1.7")
|
||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
implementation(libs.litert)
|
implementation(libs.androidx.datastore.core.android)
|
||||||
|
implementation(libs.core.ktx)
|
||||||
|
// implementation(libs.litert)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|||||||
BIN
mobile/libs/opus.aar
Normal file
BIN
mobile/src/main/assets/2024_08_16/audio-model.tflite
Normal file
6522
mobile/src/main/assets/2024_08_16/en_us.txt
Normal file
BIN
mobile/src/main/assets/2024_08_16/meta-model.tflite
Normal file
BIN
mobile/src/main/assets/metamodel_FP16.tflite
Normal file
BIN
mobile/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 507 KiB |
@@ -1,189 +0,0 @@
|
|||||||
package com.birdsounds.identify;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
|
||||||
public class Downloader {
|
|
||||||
static final String modelFILE = "model.tflite";
|
|
||||||
static final String metaModelFILE = "metaModel.tflite";
|
|
||||||
static final String modelURL = "https://raw.githubusercontent.com/woheller69/whoBIRD-TFlite/master/BirdNET_GLOBAL_6K_V2.4_Model_FP16.tflite";
|
|
||||||
static final String model32URL = "https://raw.githubusercontent.com/woheller69/whoBIRD-TFlite/master/BirdNET_GLOBAL_6K_V2.4_Model_FP32.tflite";
|
|
||||||
static final String metaModelURL = "https://raw.githubusercontent.com/woheller69/whoBIRD-TFlite/master/BirdNET_GLOBAL_6K_V2.4_MData_Model_FP16.tflite";
|
|
||||||
static final String modelMD5 = "b1c981fe261910b473b9b7eec9ebcd4e";
|
|
||||||
static final String model32MD5 = "6c7c42106e56550fc8563adb31bc120e";
|
|
||||||
static final String metaModelMD5 ="f1a078ae0f244a1ff5a8f1ccb645c805";
|
|
||||||
|
|
||||||
public static boolean checkModels(final Activity activity) {
|
|
||||||
File modelFile = new File(activity.getDir("filesdir", Context.MODE_PRIVATE) + "/" + modelFILE);
|
|
||||||
File metaModelFile = new File(activity.getDir("filesdir", Context.MODE_PRIVATE) + "/" + metaModelFILE);
|
|
||||||
String calcModelMD5 = "";
|
|
||||||
String calcMetaModelMD5 = "";
|
|
||||||
if (modelFile.exists()) {
|
|
||||||
try {
|
|
||||||
byte[] data = Files.readAllBytes(Paths.get(modelFile.getPath()));
|
|
||||||
byte[] hash = MessageDigest.getInstance("MD5").digest(data);
|
|
||||||
calcModelMD5 = new BigInteger(1, hash).toString(16);
|
|
||||||
} catch (IOException | NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (metaModelFile.exists()) {
|
|
||||||
try {
|
|
||||||
byte[] data = Files.readAllBytes(Paths.get(metaModelFile.getPath()));
|
|
||||||
byte[] hash = MessageDigest.getInstance("MD5").digest(data);
|
|
||||||
calcMetaModelMD5 = new BigInteger(1, hash).toString(16);
|
|
||||||
} catch (IOException | NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modelFile.exists() && !(calcModelMD5.equals(modelMD5) || calcModelMD5.equals(model32MD5))) modelFile.delete();
|
|
||||||
if (metaModelFile.exists() && !calcMetaModelMD5.equals(metaModelMD5)) metaModelFile.delete();
|
|
||||||
|
|
||||||
return (calcModelMD5.equals(modelMD5) || calcModelMD5.equals(model32MD5)) && calcMetaModelMD5.equals(metaModelMD5);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void downloadModels(final Activity activity) {
|
|
||||||
File modelFile = new File(activity.getDir("filesdir", Context.MODE_PRIVATE) + "/" + modelFILE);
|
|
||||||
Log.d("Heyy","Model file checking");
|
|
||||||
if (!modelFile.exists()) {
|
|
||||||
Log.d("whoBIRD", "model file does not exist");
|
|
||||||
Thread thread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
URL url;
|
|
||||||
if (false) url = new URL(model32URL);
|
|
||||||
else url = new URL(modelURL);
|
|
||||||
|
|
||||||
Log.d("whoBIRD", "Download model");
|
|
||||||
|
|
||||||
URLConnection ucon = url.openConnection();
|
|
||||||
ucon.setReadTimeout(5000);
|
|
||||||
ucon.setConnectTimeout(10000);
|
|
||||||
|
|
||||||
InputStream is = ucon.getInputStream();
|
|
||||||
BufferedInputStream inStream = new BufferedInputStream(is, 1024 * 5);
|
|
||||||
|
|
||||||
modelFile.createNewFile();
|
|
||||||
|
|
||||||
FileOutputStream outStream = new FileOutputStream(modelFile);
|
|
||||||
byte[] buff = new byte[5 * 1024];
|
|
||||||
|
|
||||||
int len;
|
|
||||||
while ((len = inStream.read(buff)) != -1) {
|
|
||||||
outStream.write(buff, 0, len);
|
|
||||||
}
|
|
||||||
outStream.flush();
|
|
||||||
outStream.close();
|
|
||||||
inStream.close();
|
|
||||||
|
|
||||||
String calcModelMD5="";
|
|
||||||
if (modelFile.exists()) {
|
|
||||||
byte[] data = Files.readAllBytes(Paths.get(modelFile.getPath()));
|
|
||||||
byte[] hash = MessageDigest.getInstance("MD5").digest(data);
|
|
||||||
calcModelMD5 = new BigInteger(1, hash).toString(16);
|
|
||||||
} else {
|
|
||||||
throw new IOException(); //throw exception if there is no modelFile at this point
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(calcModelMD5.equals(modelMD5) || calcModelMD5.equals(model32MD5) )){
|
|
||||||
modelFile.delete();
|
|
||||||
activity.runOnUiThread(() -> {
|
|
||||||
Toast.makeText(activity, activity.getResources().getString(R.string.error_download), Toast.LENGTH_SHORT).show();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
activity.runOnUiThread(() -> {
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException | IOException i) {
|
|
||||||
activity.runOnUiThread(() -> Toast.makeText(activity, activity.getResources().getString(R.string.error_download), Toast.LENGTH_SHORT).show());
|
|
||||||
modelFile.delete();
|
|
||||||
Log.w("whoBIRD", activity.getResources().getString(R.string.error_download), i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread.start();
|
|
||||||
} else {
|
|
||||||
Log.d("whoBIRD","model exists");
|
|
||||||
activity.runOnUiThread(() -> {
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
File metaModelFile = new File(activity.getDir("filesdir", Context.MODE_PRIVATE) + "/" + metaModelFILE);
|
|
||||||
if (!metaModelFile.exists()) {
|
|
||||||
Log.d("whoBIRD", "meta model file does not exist");
|
|
||||||
Thread thread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
URL url = new URL(metaModelURL);
|
|
||||||
Log.d("whoBIRD", "Download meta model");
|
|
||||||
|
|
||||||
URLConnection ucon = url.openConnection();
|
|
||||||
ucon.setReadTimeout(5000);
|
|
||||||
ucon.setConnectTimeout(10000);
|
|
||||||
|
|
||||||
InputStream is = ucon.getInputStream();
|
|
||||||
BufferedInputStream inStream = new BufferedInputStream(is, 1024 * 5);
|
|
||||||
|
|
||||||
metaModelFile.createNewFile();
|
|
||||||
|
|
||||||
FileOutputStream outStream = new FileOutputStream(metaModelFile);
|
|
||||||
byte[] buff = new byte[5 * 1024];
|
|
||||||
|
|
||||||
int len;
|
|
||||||
while ((len = inStream.read(buff)) != -1) {
|
|
||||||
outStream.write(buff, 0, len);
|
|
||||||
}
|
|
||||||
outStream.flush();
|
|
||||||
outStream.close();
|
|
||||||
inStream.close();
|
|
||||||
|
|
||||||
String calcMetaModelMD5="";
|
|
||||||
if (metaModelFile.exists()) {
|
|
||||||
byte[] data = Files.readAllBytes(Paths.get(metaModelFile.getPath()));
|
|
||||||
byte[] hash = MessageDigest.getInstance("MD5").digest(data);
|
|
||||||
calcMetaModelMD5 = new BigInteger(1, hash).toString(16);
|
|
||||||
} else {
|
|
||||||
throw new IOException(); //throw exception if there is no modelFile at this point
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!calcMetaModelMD5.equals(metaModelMD5)){
|
|
||||||
metaModelFile.delete();
|
|
||||||
activity.runOnUiThread(() -> {
|
|
||||||
Toast.makeText(activity, activity.getResources().getString(R.string.error_download), Toast.LENGTH_SHORT).show();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
activity.runOnUiThread(() -> {
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException | IOException i) {
|
|
||||||
activity.runOnUiThread(() -> Toast.makeText(activity, activity.getResources().getString(R.string.error_download), Toast.LENGTH_SHORT).show());
|
|
||||||
metaModelFile.delete();
|
|
||||||
Log.w("whoBIRD", activity.getResources().getString(R.string.error_download), i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread.start();
|
|
||||||
} else {
|
|
||||||
Log.d("whoBIRD", "meta file exists");
|
|
||||||
activity.runOnUiThread(() -> {
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package com.birdsounds.identify;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.location.LocationListener;
|
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
|
|
||||||
public class Location {
|
|
||||||
|
|
||||||
private static LocationListener locationListenerGPS;
|
|
||||||
|
|
||||||
static void stopLocation(Context context){
|
|
||||||
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
if (locationListenerGPS!=null) locationManager.removeUpdates(locationListenerGPS);
|
|
||||||
locationListenerGPS=null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void requestLocation(Context context, SoundClassifier soundClassifier) {
|
|
||||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && checkLocationProvider(context)) {
|
|
||||||
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
if (locationListenerGPS==null) locationListenerGPS = new LocationListener() {
|
|
||||||
@Override
|
|
||||||
public void onLocationChanged(android.location.Location location) {
|
|
||||||
soundClassifier.runMetaInterpreter(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
|
||||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProviderEnabled(String provider) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProviderDisabled(String provider) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 60000, 0, locationListenerGPS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean checkLocationProvider(Context context) {
|
|
||||||
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)){
|
|
||||||
Toast.makeText(context, "Error no GPS", Toast.LENGTH_SHORT).show();
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package com.birdsounds.identify
|
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.Manifest
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import com.google.android.gms.wearable.ChannelClient
|
|
||||||
import com.google.android.gms.wearable.Wearable
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
|
||||||
// private lateinit var soundClassifier: SoundClassifier
|
|
||||||
val REQUEST_PERMISSIONS = 1337
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
enableEdgeToEdge()
|
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
Wearable.getChannelClient(this.applicationContext)
|
|
||||||
.registerChannelCallback(object : ChannelClient.ChannelCallback() {
|
|
||||||
override fun onChannelOpened(channel: ChannelClient.Channel) {
|
|
||||||
super.onChannelOpened(channel)
|
|
||||||
Log.d("HEY", "onChannelOpened")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Downloader.downloadModels(this)
|
|
||||||
requestPermissions()
|
|
||||||
soundClassifier = SoundClassifier(this, SoundClassifier.Options())
|
|
||||||
Location.requestLocation(this, soundClassifier)
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
|
||||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
||||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
|
||||||
insets
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
companion object {
|
|
||||||
var soundClassifier: SoundClassifier? = null
|
|
||||||
// fun getSoundClassifier(): SoundClassifier? {
|
|
||||||
// return soundClassifier
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requestPermissions() {
|
|
||||||
val perms = mutableListOf<String>()
|
|
||||||
|
|
||||||
if (ContextCompat.checkSelfPermission(
|
|
||||||
this,
|
|
||||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
|
||||||
) != PackageManager.PERMISSION_GRANTED
|
|
||||||
) {
|
|
||||||
perms.add(Manifest.permission.ACCESS_COARSE_LOCATION)
|
|
||||||
perms.add(Manifest.permission.ACCESS_FINE_LOCATION)
|
|
||||||
}
|
|
||||||
if (!perms.isEmpty()) requestPermissions(perms.toTypedArray(), REQUEST_PERMISSIONS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package com.birdsounds.identify
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.util.Half.abs
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
|
||||||
import com.google.android.gms.wearable.MessageEvent
|
|
||||||
import com.google.android.gms.wearable.WearableListenerService
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
import java.nio.ShortBuffer
|
|
||||||
|
|
||||||
class MessageListenerService : WearableListenerService() {
|
|
||||||
private val tag = "MessageListenerService"
|
|
||||||
|
|
||||||
// fun placeSoundClassifier(soundClassifier: SoundClassifier)
|
|
||||||
override fun onMessageReceived(p0: MessageEvent) {
|
|
||||||
super.onMessageReceived(p0)
|
|
||||||
|
|
||||||
// MainActivity
|
|
||||||
val soundclassifier = MainActivity.soundClassifier
|
|
||||||
if (soundclassifier == null) {
|
|
||||||
Log.w(tag, "Have invalid sound classifier")
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
Log.w(tag, "Have valid classifier")
|
|
||||||
}
|
|
||||||
val short_array = ShortArray(48000 * 3)
|
|
||||||
var tstamp_bytes = p0.data.copyOfRange(0, Long.SIZE_BYTES)
|
|
||||||
var audio_bytes = p0.data.copyOfRange(Long.SIZE_BYTES, p0.data.size)
|
|
||||||
var string_send: String = ""
|
|
||||||
|
|
||||||
ByteBuffer.wrap(audio_bytes).order(
|
|
||||||
ByteOrder.LITTLE_ENDIAN
|
|
||||||
).asShortBuffer().get(short_array)
|
|
||||||
Log.w(tag, short_array.sum().toString())
|
|
||||||
var sorted_list = soundclassifier.executeScoring(short_array)
|
|
||||||
Log.w(tag, "")
|
|
||||||
for (i in 0 until 5) {
|
|
||||||
val score = sorted_list[i].value
|
|
||||||
val index = sorted_list[i].index
|
|
||||||
val species_name = soundclassifier.labelList[index]
|
|
||||||
Log.w(tag, species_name + ", " + score.toString())
|
|
||||||
string_send+= species_name
|
|
||||||
string_send+=','
|
|
||||||
string_send+=score.toString()
|
|
||||||
string_send+=';'
|
|
||||||
}
|
|
||||||
MessageSenderFromPhone.sendMessage("/audio", tstamp_bytes + string_send.toByteArray(), this)
|
|
||||||
// Log.i(tag , short_array.map( { abs(it)}).sum().toString())
|
|
||||||
// Log.i(tag, short_array[0].toString())
|
|
||||||
// Log.i(tag, p0.data.toString(Charsets.US_ASCII))
|
|
||||||
// broadcastMessage(p0)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import android.content.ContentValues.TAG
|
||||||
|
import android.media.MediaCodec
|
||||||
|
import android.media.MediaCodecInfo
|
||||||
|
import android.media.MediaCodecList
|
||||||
|
import android.media.MediaFormat
|
||||||
|
import android.util.Log
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun listMediaCodecDecoders() {
|
||||||
|
val codecList = MediaCodecList(MediaCodecList.ALL_CODECS) // Get all codecs
|
||||||
|
val codecs = codecList.codecInfos
|
||||||
|
|
||||||
|
Log.e(TAG, "Available MediaCodec Decoders:")
|
||||||
|
for (codec in codecs) {
|
||||||
|
if (!codec.isEncoder) { // Check if the codec is a decoder
|
||||||
|
Log.e(TAG, "Decoder: ${codec.name}")
|
||||||
|
// List the MIME types supported by the decoder
|
||||||
|
val supportedTypes = codec.supportedTypes
|
||||||
|
Log.e(TAG, " Supported Types:")
|
||||||
|
for (type in supportedTypes) {
|
||||||
|
Log.e(TAG, " $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun decodeAACToPCM(inputData: ByteArray): ShortArray {
|
||||||
|
// listMediaCodecDecoders();
|
||||||
|
// Media format configuration for AAC
|
||||||
|
val mediaFormat = MediaFormat.createAudioFormat(
|
||||||
|
MediaFormat.MIMETYPE_AUDIO_OPUS, // MIME type for AAC
|
||||||
|
48000, // Sample rate, change this based on your input data
|
||||||
|
1 // Channel count, change this based on your input data
|
||||||
|
)
|
||||||
|
// mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64000) // 128kbps
|
||||||
|
// mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 0) // AAC should use ADTS header
|
||||||
|
// mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
|
||||||
|
|
||||||
|
// Create a decoder for AAC
|
||||||
|
val mediaCodec = MediaCodec.createDecoderByType( MediaFormat.MIMETYPE_AUDIO_OPUS);
|
||||||
|
mediaCodec.configure(mediaFormat, null, null, 0)
|
||||||
|
mediaCodec.start()
|
||||||
|
|
||||||
|
val decodedSamples = mutableListOf<Short>()
|
||||||
|
|
||||||
|
// Variables for handling input and output buffers
|
||||||
|
|
||||||
|
val bufferInfo = MediaCodec.BufferInfo()
|
||||||
|
|
||||||
|
var inputOffset = 0;
|
||||||
|
|
||||||
|
while (inputOffset < inputData.size || true) {
|
||||||
|
// Feed input data to the codec
|
||||||
|
val inputBufferIndex = mediaCodec.dequeueInputBuffer(100000) // Timeout in microseconds
|
||||||
|
if (inputBufferIndex >= 0 && inputOffset < inputData.size) {
|
||||||
|
val inputBuffer: ByteBuffer? = mediaCodec.getInputBuffer(inputBufferIndex)
|
||||||
|
inputBuffer?.clear()
|
||||||
|
|
||||||
|
// Calculate the number of bytes to write to the buffer
|
||||||
|
val chunkSize = kotlin.math.min(inputBuffer?.capacity() ?: 0, inputData.size - inputOffset)
|
||||||
|
Log.e(TAG, "Chunk size: " + chunkSize.toString())
|
||||||
|
inputBuffer?.put(inputData, inputOffset, chunkSize)
|
||||||
|
inputOffset += chunkSize
|
||||||
|
|
||||||
|
// Pass the data to the codec
|
||||||
|
mediaCodec.queueInputBuffer(inputBufferIndex, 0, chunkSize, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process output data
|
||||||
|
val outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000)
|
||||||
|
Log.e(TAG, "Output buffer index: " + outputBufferIndex.toString())
|
||||||
|
if (outputBufferIndex >= 0) {
|
||||||
|
val outputBuffer: ByteBuffer? = mediaCodec.getOutputBuffer(outputBufferIndex)
|
||||||
|
|
||||||
|
// Convert byte buffer to PCM data (16-bit integers)
|
||||||
|
val pcmData = ShortArray(bufferInfo.size / 2)
|
||||||
|
outputBuffer?.asShortBuffer()?.get(pcmData)
|
||||||
|
|
||||||
|
// Add PCM data to the final output array
|
||||||
|
decodedSamples.addAll(pcmData.toList())
|
||||||
|
|
||||||
|
// Release the output buffer
|
||||||
|
mediaCodec.releaseOutputBuffer(outputBufferIndex, false)
|
||||||
|
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||||
|
// Handle format changes, if needed
|
||||||
|
mediaCodec.outputFormat
|
||||||
|
} else if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
|
||||||
|
// Output buffer not available, retry later
|
||||||
|
if (inputOffset >= inputData.size) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the codec when done
|
||||||
|
mediaCodec.stop()
|
||||||
|
mediaCodec.release()
|
||||||
|
|
||||||
|
return decodedSamples.toShortArray()
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.birdsounds.identify
|
||||||
|
import android.content.Context
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class Downloader(mainActivity: MainActivity) {
|
||||||
|
|
||||||
|
private val settings = Settings;
|
||||||
|
private var activity: MainActivity = mainActivity;
|
||||||
|
private var context: Context = activity.applicationContext;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun copyAssetToFolder(assetName: String, destinationPath: String): Boolean {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Get the input stream from the asset
|
||||||
|
val assetInputStream = context.assets.open(assetName)
|
||||||
|
|
||||||
|
// Create the destination directory if it doesn't exist
|
||||||
|
val destinationFile = File(activity.getDir("",Context.MODE_PRIVATE).absolutePath + "/" + destinationPath)
|
||||||
|
destinationFile.parentFile?.mkdirs()
|
||||||
|
|
||||||
|
// Copy the file
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
val outputStream = FileOutputStream(destinationFile)
|
||||||
|
var read: Int
|
||||||
|
|
||||||
|
while (assetInputStream.read(buffer).also { read = it } != -1) {
|
||||||
|
outputStream.write(buffer, 0, read)
|
||||||
|
}
|
||||||
|
|
||||||
|
assetInputStream.close()
|
||||||
|
outputStream.flush()
|
||||||
|
outputStream.close()
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun prepareModelFiles()
|
||||||
|
{
|
||||||
|
copyAssetToFolder(settings.pkg_model_file, settings.local_model_file);
|
||||||
|
copyAssetToFolder(settings.pkg_meta_model_file, settings.local_meta_model_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
111
mobile/src/main/java/com/birdsounds/identify/phone_Location.kt
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package com.birdsounds.identify
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.location.Address
|
||||||
|
import android.location.Geocoder
|
||||||
|
import android.location.Location
|
||||||
|
import android.location.LocationListener
|
||||||
|
import android.location.LocationManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
object Location {
|
||||||
|
private var locationListenerGPS: LocationListener? = null
|
||||||
|
|
||||||
|
fun stopLocation(context: Context) {
|
||||||
|
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
|
if (locationListenerGPS != null) locationManager.removeUpdates(
|
||||||
|
locationListenerGPS!!
|
||||||
|
)
|
||||||
|
locationListenerGPS = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestLocation(context: Context, soundClassifier: SoundClassifier) {
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_GRANTED && checkLocationProvider(context)
|
||||||
|
) {
|
||||||
|
val locationManager =
|
||||||
|
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
|
|
||||||
|
|
||||||
|
if (locationListenerGPS == null) locationListenerGPS = object : LocationListener {
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onLocationChanged(location: Location) {
|
||||||
|
Log.w(TAG, "Got location changed");
|
||||||
|
while (!soundClassifier.is_model_ready()) {
|
||||||
|
Thread.sleep(50);
|
||||||
|
}
|
||||||
|
Log.w(TAG, "Sound classifier is ready");
|
||||||
|
soundClassifier.runMetaInterpreter(location)
|
||||||
|
|
||||||
|
val activity = context as? Activity;
|
||||||
|
activity?.let {
|
||||||
|
|
||||||
|
val text_species: TextView = it.findViewById(R.id.local_species)
|
||||||
|
text_species.text = local_species;
|
||||||
|
|
||||||
|
val loc_lon: TextView = it.findViewById(R.id.location_long)
|
||||||
|
loc_lon.text =
|
||||||
|
"Longitude: ${location.longitude}"
|
||||||
|
|
||||||
|
val loc_lat: TextView = it.findViewById(R.id.location_lat)
|
||||||
|
loc_lat.text =
|
||||||
|
"Latitude: ${location.latitude}"
|
||||||
|
|
||||||
|
val loc_string: TextView = it.findViewById(R.id.location_string);
|
||||||
|
val geocoder = Geocoder(context, Locale.getDefault())
|
||||||
|
val addresses: MutableList<Address>? =
|
||||||
|
geocoder.getFromLocation(location.latitude, location.longitude, 1)
|
||||||
|
if (addresses?.isNotEmpty() == true) {
|
||||||
|
val address: Address = addresses[0]
|
||||||
|
|
||||||
|
// loc_string.text = address.locality.toString() + ", " + address.adminArea.toString() + " " + address.countryName.toString()
|
||||||
|
loc_string.text = address.getAddressLine(0).toString()
|
||||||
|
// Log.w(TAG, address.toString())
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
|
override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProviderEnabled(provider: String) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProviderDisabled(provider: String) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locationManager.requestLocationUpdates(
|
||||||
|
LocationManager.PASSIVE_PROVIDER, 60000, 0f,
|
||||||
|
locationListenerGPS!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkLocationProvider(context: Context): Boolean {
|
||||||
|
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
|
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||||
|
Toast.makeText(context, "Error no GPS", Toast.LENGTH_SHORT).show()
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package com.birdsounds.identify
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.preference.PreferenceManager
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.SeekBar
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.google.android.gms.wearable.ChannelClient
|
||||||
|
import com.google.android.gms.wearable.Wearable
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
private var updateJob: Job? = null
|
||||||
|
private var updateCounter = 0
|
||||||
|
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val Any.TAG: String
|
||||||
|
get() {
|
||||||
|
val tag = javaClass.simpleName
|
||||||
|
return if (tag.length <= 23) tag else tag.substring(0, 23)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SynchronousDataStore(private val context: Context) {
|
||||||
|
|
||||||
|
// Define keys
|
||||||
|
companion object {
|
||||||
|
val THRESHOLD_KEY = intPreferencesKey("user_age")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronous write operations
|
||||||
|
fun saveThreshold(value: Int) {
|
||||||
|
runBlocking {
|
||||||
|
context.dataStore.edit { preferences ->
|
||||||
|
preferences[THRESHOLD_KEY] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getThreshold(): Int {
|
||||||
|
|
||||||
|
return runBlocking {
|
||||||
|
context.dataStore.data.first()[THRESHOLD_KEY] ?: 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
// private lateinit var soundClassifier: SoundClassifier
|
||||||
|
val REQUEST_PERMISSIONS = 1337
|
||||||
|
|
||||||
|
private lateinit var dataStore: SynchronousDataStore
|
||||||
|
|
||||||
|
private lateinit var last_message_delay: TextView
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
|
Wearable.getChannelClient(this.applicationContext)
|
||||||
|
.registerChannelCallback(object : ChannelClient.ChannelCallback() {
|
||||||
|
override fun onChannelOpened(channel: ChannelClient.Channel) {
|
||||||
|
super.onChannelOpened(channel)
|
||||||
|
Log.d(TAG, "onChannelOpened")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Downloader(this).prepareModelFiles();
|
||||||
|
Log.w(TAG, "Finished setting up downloader")
|
||||||
|
requestPermissions()
|
||||||
|
soundClassifier = SoundClassifier(this, SoundClassifier.Options())
|
||||||
|
Log.w(TAG, "Starting sound classifier")
|
||||||
|
Location.requestLocation(this, soundClassifier!!)
|
||||||
|
Log.w(TAG, "Starting location requester")
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||||
|
insets
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val thresholdText = findViewById<TextView>(R.id.threshold_value_text)
|
||||||
|
val seekBar = findViewById<SeekBar>(R.id.threshold_set_scale_bar)
|
||||||
|
last_message_delay = findViewById<TextView>(R.id.last_message_delay)
|
||||||
|
dataStore = SynchronousDataStore(applicationContext)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Settings.threshold = dataStore.getThreshold();
|
||||||
|
seekBar.progress = Settings.threshold
|
||||||
|
thresholdText.text = String.format("%.2f", Settings.threshold/100f)
|
||||||
|
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||||
|
|
||||||
|
var actualValue = progress
|
||||||
|
dataStore.saveThreshold(actualValue);
|
||||||
|
Settings.threshold = actualValue;
|
||||||
|
thresholdText.text = String.format("%.2f", actualValue/100f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
|
||||||
|
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
var soundClassifier: SoundClassifier? = null
|
||||||
|
// fun getSoundClassifier(): SoundClassifier? {
|
||||||
|
// return soundClassifier
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
// Start periodic updates using coroutines
|
||||||
|
updateJob = lifecycleScope.launch {
|
||||||
|
while (isActive) { // isActive is a property of the coroutine scope
|
||||||
|
updateCounter++
|
||||||
|
if (last_message_time == 0.toLong())
|
||||||
|
{
|
||||||
|
last_message_delay.text = "No messages received"
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
last_message_delay.text = "Last message: ${(Instant.now().toEpochMilli() - last_message_time)/1000F.toInt()} seconds ago"
|
||||||
|
}
|
||||||
|
|
||||||
|
// last_message_delay.text = "Update count: $updateCounter"
|
||||||
|
delay(500) // Update every 1 second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
// Cancel the coroutine when activity is not visible
|
||||||
|
updateJob?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestPermissions() {
|
||||||
|
val perms = mutableListOf<String>()
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
perms.add(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
|
perms.add(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
|
}
|
||||||
|
if (perms.isNotEmpty()) requestPermissions(perms.toTypedArray(), REQUEST_PERMISSIONS)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
package com.birdsounds.identify
|
package com.birdsounds.identify
|
||||||
import android.content.Intent
|
|
||||||
|
|
||||||
object MessageConstants {
|
object MessageConstants {
|
||||||
const val intentName = "WearableMessageDisplay"
|
const val intentName = "WearableMessageDisplay"
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package com.birdsounds.identify
|
||||||
|
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.android.gms.wearable.MessageEvent
|
||||||
|
import com.google.android.gms.wearable.WearableListenerService
|
||||||
|
import com.theeasiestway.opus.Constants
|
||||||
|
import com.theeasiestway.opus.Opus
|
||||||
|
import decodeAACToPCM
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
|
||||||
|
var last_message_time = 0L;
|
||||||
|
fun ByteArray.toLong(): Long {
|
||||||
|
require(size <= 8) { "ByteArray too large to fit in Long" }
|
||||||
|
|
||||||
|
var result = 0L
|
||||||
|
for (byte in this) {
|
||||||
|
result = (result shl 8) or (byte.toLong() and 0xFF)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageListenerService : WearableListenerService() {
|
||||||
|
|
||||||
|
|
||||||
|
// fun placeSoundClassifier(soundClassifier: SoundClassifier)
|
||||||
|
override fun onMessageReceived(p0: MessageEvent) {
|
||||||
|
super.onMessageReceived(p0)
|
||||||
|
val codec_opus = Opus()
|
||||||
|
codec_opus.decoderInit(Constants.SampleRate._48000(), Constants.Channels.mono())
|
||||||
|
// MainActivity
|
||||||
|
Log.w(TAG, "Data recv: "+p0.data.size.toString() + " bytes")
|
||||||
|
val soundclassifier = MainActivity.soundClassifier
|
||||||
|
if (soundclassifier == null) {
|
||||||
|
Log.w(TAG, "Have invalid sound classifier")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Have valid classifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
var tstamp_bytes = p0.data.copyOfRange(0, Long.SIZE_BYTES)
|
||||||
|
|
||||||
|
last_message_time = tstamp_bytes.toLong()
|
||||||
|
|
||||||
|
|
||||||
|
var audio_bytes_og = p0.data.copyOfRange(Long.SIZE_BYTES, p0.data.size)
|
||||||
|
|
||||||
|
|
||||||
|
val buffer = ByteBuffer.wrap(audio_bytes_og)
|
||||||
|
val sound_a = ByteArrayOutputStream();
|
||||||
|
val byteArrayList = mutableListOf<ByteArray>()
|
||||||
|
while (buffer.hasRemaining())
|
||||||
|
{
|
||||||
|
val num_to_read = buffer.get().toInt()
|
||||||
|
val read_this = ByteArray(num_to_read)
|
||||||
|
buffer.get(read_this)
|
||||||
|
val decoded = codec_opus.decode(read_this, Constants.FrameSize._120())
|
||||||
|
sound_a.write(decoded);
|
||||||
|
// Log.e(TAG,"Decompressed ${read_this.size} to ${decoded?.size}")
|
||||||
|
}
|
||||||
|
val audio_bytes = sound_a.toByteArray()
|
||||||
|
|
||||||
|
codec_opus.decoderRelease();
|
||||||
|
|
||||||
|
val short_array = ShortArray(audio_bytes.size/2)
|
||||||
|
// Log.e(TAG,"Size of short array buffer: "+ decoded?.size.toString());
|
||||||
|
|
||||||
|
ByteBuffer.wrap(audio_bytes).order(
|
||||||
|
ByteOrder.LITTLE_ENDIAN
|
||||||
|
).asShortBuffer().get(short_array)
|
||||||
|
// Log.e(TAG, pcm_byte_array.sum().toString())
|
||||||
|
Log.e(TAG, "STARTING SCORING");
|
||||||
|
|
||||||
|
|
||||||
|
var string_send: String = ""
|
||||||
|
var sorted_list = soundclassifier.executeScoring(short_array)
|
||||||
|
Log.w(TAG, "FINISHED SCORING");
|
||||||
|
Log.w(TAG, "")
|
||||||
|
val threshold = Settings.threshold/100f
|
||||||
|
for (i in 0 until 10) {
|
||||||
|
val score = sorted_list[i].value
|
||||||
|
if (score < threshold) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val index = sorted_list[i].index
|
||||||
|
val species_name = soundclassifier.labelList[index]
|
||||||
|
Log.w(TAG, species_name + ", " + score.toString())
|
||||||
|
string_send+= species_name
|
||||||
|
string_send+=','
|
||||||
|
string_send+=score.toString()
|
||||||
|
string_send+=';'
|
||||||
|
}
|
||||||
|
MessageSenderFromPhone.sendMessage("/audio", tstamp_bytes + string_send.toByteArray(), this)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.birdsounds.identify
|
||||||
|
|
||||||
|
object Settings {
|
||||||
|
var local_model_file: String = "2024_08_16_audio_model.tflite"
|
||||||
|
var pkg_model_file: String = "2024_08_16/audio-model.tflite"
|
||||||
|
|
||||||
|
var local_meta_model_file: String = "2024_08_16_meta_model.tflite"
|
||||||
|
var pkg_meta_model_file: String = "2024_08_16/meta-model.tflite"
|
||||||
|
|
||||||
|
var threshold: Int = 50;
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.birdsounds.identify
|
package com.birdsounds.identify
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.Nullable
|
|
||||||
import org.tensorflow.lite.Interpreter
|
import org.tensorflow.lite.Interpreter
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -23,16 +23,20 @@ import kotlin.concurrent.scheduleAtFixedRate
|
|||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import uk.me.berndporr.iirj.Butterworth
|
import uk.me.berndporr.iirj.Butterworth
|
||||||
import java.nio.ShortBuffer
|
|
||||||
import kotlin.math.round
|
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var local_species = ""
|
||||||
class SoundClassifier(
|
class SoundClassifier(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val options: Options = Options()
|
private val options: Options = Options()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
internal var mContext: Context
|
internal var mContext: Context
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val TAG = "Sound Classifier"
|
val TAG = "Sound Classifier"
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -40,14 +44,10 @@ class SoundClassifier(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Options(
|
class Options(
|
||||||
/** Path of the converted model label file, relative to the assets/ directory. */
|
|
||||||
val labelsBase: String = "labels",
|
val labelsBase: String = "labels",
|
||||||
/** Path of the converted .tflite file, relative to the assets/ directory. */
|
/** Path of the converted .tflite file, relative to the assets/ directory. */
|
||||||
val assetFile: String = "assets.txt",
|
val assetFile: String = "assets.txt",
|
||||||
/** Path of the converted .tflite file, relative to the assets/ directory. */
|
/** Path of the converted .tflite file, relative to the assets/ directory. */
|
||||||
val modelPath: String = "model.tflite",
|
|
||||||
/** Path of the meta model .tflite file, relative to the assets/ directory. */
|
|
||||||
val metaModelPath: String = "metaModel.tflite",
|
|
||||||
/** The required audio sample rate in Hz. */
|
/** The required audio sample rate in Hz. */
|
||||||
val sampleRate: Int = 48000,
|
val sampleRate: Int = 48000,
|
||||||
/** Multiplier for audio samples */
|
/** Multiplier for audio samples */
|
||||||
@@ -83,7 +83,7 @@ class SoundClassifier(
|
|||||||
/** Number of output classes of the TFLite model. */
|
/** Number of output classes of the TFLite model. */
|
||||||
private var modelNumClasses = 0
|
private var modelNumClasses = 0
|
||||||
private var metaModelNumClasses = 0
|
private var metaModelNumClasses = 0
|
||||||
|
private var settings = Settings;
|
||||||
|
|
||||||
/** Used to hold the real-time probabilities predicted by the model for the output classes. */
|
/** Used to hold the real-time probabilities predicted by the model for the output classes. */
|
||||||
private lateinit var predictionProbs: FloatArray
|
private lateinit var predictionProbs: FloatArray
|
||||||
@@ -94,19 +94,26 @@ class SoundClassifier(
|
|||||||
|
|
||||||
private var recognitionTask: TimerTask? = null
|
private var recognitionTask: TimerTask? = null
|
||||||
|
|
||||||
|
|
||||||
/** Buffer that holds audio PCM sample that are fed to the TFLite model for inference. */
|
/** Buffer that holds audio PCM sample that are fed to the TFLite model for inference. */
|
||||||
private lateinit var inputBuffer: FloatBuffer
|
private lateinit var inputBuffer: FloatBuffer
|
||||||
private lateinit var metaInputBuffer: FloatBuffer
|
private lateinit var metaInputBuffer: FloatBuffer
|
||||||
|
private var model_ready = false;
|
||||||
init {
|
init {;
|
||||||
|
setupDecoder(context)
|
||||||
loadLabels(context)
|
loadLabels(context)
|
||||||
loadAssetList(context)
|
loadAssetList(context)
|
||||||
setupInterpreter(context)
|
setupInterpreter(context)
|
||||||
setupMetaInterpreter(context)
|
setupMetaInterpreter(context)
|
||||||
warmUpModel()
|
warmUpModel()
|
||||||
|
this.model_ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun is_model_ready(): Boolean
|
||||||
|
{
|
||||||
|
return this.model_ready;
|
||||||
|
}
|
||||||
|
private fun setupDecoder(context: Context) {
|
||||||
|
}
|
||||||
|
|
||||||
/** Retrieve asset list from "asset_list" file */
|
/** Retrieve asset list from "asset_list" file */
|
||||||
private fun loadAssetList(context: Context) {
|
private fun loadAssetList(context: Context) {
|
||||||
@@ -168,10 +175,12 @@ class SoundClassifier(
|
|||||||
|
|
||||||
private fun setupInterpreter(context: Context) {
|
private fun setupInterpreter(context: Context) {
|
||||||
try {
|
try {
|
||||||
val modelFilePath = context.getDir(
|
val modelFilePath =
|
||||||
"filesdir",
|
context.getDir(
|
||||||
|
"",
|
||||||
Context.MODE_PRIVATE
|
Context.MODE_PRIVATE
|
||||||
).absolutePath + "/" + options.modelPath
|
).absolutePath + "/" + settings.local_model_file;
|
||||||
|
|
||||||
Log.i(TAG, "Trying to create TFLite buffer from $modelFilePath")
|
Log.i(TAG, "Trying to create TFLite buffer from $modelFilePath")
|
||||||
val modelFile = File(modelFilePath)
|
val modelFile = File(modelFilePath)
|
||||||
val tfliteBuffer: ByteBuffer =
|
val tfliteBuffer: ByteBuffer =
|
||||||
@@ -211,9 +220,9 @@ class SoundClassifier(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
val metaModelFilePath = context.getDir(
|
val metaModelFilePath = context.getDir(
|
||||||
"filesdir",
|
"",
|
||||||
Context.MODE_PRIVATE
|
Context.MODE_PRIVATE
|
||||||
).absolutePath + "/" + options.metaModelPath
|
).absolutePath + "/" + settings.local_meta_model_file
|
||||||
Log.i(TAG, "Trying to create TFLite buffer from $metaModelFilePath")
|
Log.i(TAG, "Trying to create TFLite buffer from $metaModelFilePath")
|
||||||
val metaModelFile = File(metaModelFilePath)
|
val metaModelFile = File(metaModelFilePath)
|
||||||
val tfliteBuffer: ByteBuffer =
|
val tfliteBuffer: ByteBuffer =
|
||||||
@@ -244,6 +253,7 @@ class SoundClassifier(
|
|||||||
}
|
}
|
||||||
// Fill the array with 1 initially.
|
// Fill the array with 1 initially.
|
||||||
metaPredictionProbs = FloatArray(metaModelNumClasses) { 1f }
|
metaPredictionProbs = FloatArray(metaModelNumClasses) { 1f }
|
||||||
|
|
||||||
metaInputBuffer = FloatBuffer.allocate(metaModelInputLength)
|
metaInputBuffer = FloatBuffer.allocate(metaModelInputLength)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -266,6 +276,17 @@ class SoundClassifier(
|
|||||||
metaOutputBuffer.rewind()
|
metaOutputBuffer.rewind()
|
||||||
metaOutputBuffer.get(metaPredictionProbs) // Copy data to metaPredictionProbs.
|
metaOutputBuffer.get(metaPredictionProbs) // Copy data to metaPredictionProbs.
|
||||||
|
|
||||||
|
var cloned = metaPredictionProbs.clone()
|
||||||
|
|
||||||
|
val sorted_indices = cloned.withIndex().sortedByDescending { it.value }.map{it.index}
|
||||||
|
|
||||||
|
local_species = "Most likely:\n"
|
||||||
|
for (i in 0..5) {
|
||||||
|
local_species += " " +labelList[sorted_indices[i]].split("_")[1]
|
||||||
|
local_species += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (i in metaPredictionProbs.indices) {
|
for (i in metaPredictionProbs.indices) {
|
||||||
metaPredictionProbs[i] =
|
metaPredictionProbs[i] =
|
||||||
@@ -333,7 +354,6 @@ class SoundClassifier(
|
|||||||
else inputBuffer.put(i, butterworth.filter(s.toDouble()).toFloat())
|
else inputBuffer.put(i, butterworth.filter(s.toDouble()).toFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inputBuffer.rewind()
|
inputBuffer.rewind()
|
||||||
outputBuffer.rewind()
|
outputBuffer.rewind()
|
||||||
interpreter.run(inputBuffer, outputBuffer)
|
interpreter.run(inputBuffer, outputBuffer)
|
||||||
@@ -1,170 +1,74 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
|
android:width="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path
|
<path android:fillColor="#3DDC84"
|
||||||
android:fillColor="#3DDC84"
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||||
android:pathData="M9,0L9,108"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M19,0L19,108"
|
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||||
android:pathData="M29,0L29,108"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M39,0L39,108"
|
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||||
android:pathData="M49,0L49,108"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M59,0L59,108"
|
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||||
android:pathData="M69,0L69,108"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M79,0L79,108"
|
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||||
android:pathData="M89,0L89,108"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M99,0L99,108"
|
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||||
android:pathData="M0,9L108,9"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M0,19L108,19"
|
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||||
android:pathData="M0,29L108,29"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -7,13 +7,89 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Hello World!"
|
android:paddingLeft="5dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:paddingTop="15dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
android:text="Threshold:"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:textSize="34sp" />
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="55dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/threshold_value_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Threshold:"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="28sp" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/threshold_set_scale_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center|fill_horizontal|fill_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:max="100"
|
||||||
|
android:min="0" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/location_string"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:paddingTop="15dp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:text="Location:"
|
||||||
|
android:textSize="34sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/location_lat"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:paddingTop="15dp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:text="Latitude:"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/location_long"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:text="Longitude:"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/local_species"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:text="Local Species:"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/last_message_delay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Last Message" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 5.3 KiB |
BIN
mobile/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 2.9 KiB |
BIN
mobile/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 8.6 KiB |
BIN
mobile/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 17 KiB |
BIN
mobile/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 28 KiB |
BIN
mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 33 KiB |
@@ -39,22 +39,25 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4")
|
|
||||||
implementation("androidx.compose.ui:ui-tooling:1.3.1")
|
api(fileTree("libs") {
|
||||||
implementation("androidx.navigation:navigation-compose:2.8.0-rc01")
|
include("*.jar")
|
||||||
implementation("androidx.wear.compose:compose-navigation:1.3.1")
|
})
|
||||||
implementation("com.google.android.horologist:horologist-audio-ui:0.6.18")
|
api(files("libs/opus.aar"))
|
||||||
implementation("com.google.android.horologist:horologist-audio:0.6.18")
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
implementation("com.google.android.horologist:horologist-compose-tools:0.6.18")
|
implementation(libs.ui.tooling)
|
||||||
implementation("com.google.android.horologist:horologist-compose-tools:0.6.18")
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation("com.google.android.horologist:horologist-compose-layout:0.6.18")
|
implementation(libs.androidx.compose.navigation.v131)
|
||||||
implementation("androidx.compose.material:material-icons-core:1.6.8")
|
implementation(libs.horologist.audio.ui)
|
||||||
implementation("androidx.compose.material:material-icons-extended:1.6.8")
|
implementation(libs.horologist.audio)
|
||||||
implementation("com.google.android.horologist:horologist-compose-material:0.6.8")
|
implementation(libs.horologist.compose.tools)
|
||||||
implementation("com.google.android.horologist:horologist-media-ui:0.6.8")
|
implementation(libs.androidx.material.icons.core)
|
||||||
implementation("com.google.android.horologist:horologist-media-data:0.6.8")
|
implementation(libs.androidx.material.icons.extended)
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.8.1")
|
implementation(libs.horologist.compose.material)
|
||||||
implementation("androidx.media3:media3-exoplayer:1.4.0")
|
implementation(libs.horologist.media.ui)
|
||||||
|
implementation(libs.horologist.media.data)
|
||||||
|
implementation(libs.kotlinx.coroutines.play.services)
|
||||||
|
implementation(libs.androidx.media3.exoplayer)
|
||||||
implementation(libs.play.services.wearable)
|
implementation(libs.play.services.wearable)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
@@ -62,14 +65,20 @@ dependencies {
|
|||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.compose.material)
|
implementation(libs.androidx.compose.material)
|
||||||
implementation(libs.androidx.compose.foundation)
|
implementation(libs.androidx.compose.foundation)
|
||||||
|
// implementation("androidx.compose.foundation:foundation:1.8.0-rc01")
|
||||||
implementation(libs.androidx.wear.tooling.preview)
|
implementation(libs.androidx.wear.tooling.preview)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.core.splashscreen)
|
implementation(libs.androidx.core.splashscreen)
|
||||||
implementation(libs.androidx.compose.navigation)
|
implementation(libs.androidx.compose.navigation)
|
||||||
implementation(libs.androidx.media3.common)
|
implementation(libs.androidx.media3.common)
|
||||||
implementation("co.touchlab:stately-concurrent-collections:2.0.0")
|
implementation(libs.androidx.wear.ongoing)
|
||||||
|
implementation(libs.accompanist.flowlayout)
|
||||||
|
implementation(libs.stately.concurrent.collections)
|
||||||
implementation(libs.androidx.compose.material3)
|
implementation(libs.androidx.compose.material3)
|
||||||
implementation(libs.androidx.work.runtime.ktx) // androidTestImplementation(platform(libs.androidx.compose.bom))
|
implementation(libs.androidx.work.runtime.ktx)
|
||||||
|
implementation(libs.androidx.runtime.android)
|
||||||
|
implementation(libs.androidx.animation.core.android)
|
||||||
|
// androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
// androidTestImplementation(libs.androidx.ui.test.junit4)
|
// androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
// debugImplementation(libs.androidx.ui.tooling)
|
// debugImplementation(libs.androidx.ui.tooling)
|
||||||
// debugImplementation(libs.androidx.ui.test.manifest)
|
// debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
|
|||||||
BIN
wear/libs/opus.aar
Normal file
@@ -1,7 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-feature android:name="android.hardware.type.watch" />
|
<uses-feature android:name="android.hardware.type.watch" />
|
||||||
|
|
||||||
@@ -26,6 +28,8 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".presentation.MainActivity"
|
android:name=".presentation.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:alwaysRetainTaskState="true"
|
||||||
|
android:immersive="true"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/MainActivityTheme.Starting">
|
android:theme="@style/MainActivityTheme.Starting">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
BIN
wear/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 507 KiB |
@@ -1,67 +0,0 @@
|
|||||||
package com.birdsounds.identify.presentation
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import androidx.wear.compose.material.CompactChip
|
|
||||||
import androidx.wear.compose.material.MaterialTheme
|
|
||||||
import androidx.wear.compose.material.Text
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ControlDashboard(controlDashboardUiState: ControlDashboardUiState,
|
|
||||||
onMicClicked: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
navController: NavHostController) {
|
|
||||||
Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxSize()) {
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
|
|
||||||
ControlDashboardButton(buttonState = controlDashboardUiState.micState,
|
|
||||||
onClick = onMicClicked,
|
|
||||||
labelText = if (controlDashboardUiState.micState.expanded) {
|
|
||||||
"Stop"
|
|
||||||
} else {
|
|
||||||
"Start"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ControlDashboardButton(buttonState: ControlDashboardButtonUiState,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
labelText: String,
|
|
||||||
modifier: Modifier = Modifier) {
|
|
||||||
CompactChip(modifier = Modifier,
|
|
||||||
onClick = onClick,
|
|
||||||
enabled = true,
|
|
||||||
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
|
||||||
shape = MaterialTheme.shapes.small,
|
|
||||||
label = {
|
|
||||||
Text(text = labelText)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Button(modifier = modifier, enabled = buttonState.enabled && buttonState.visible, onClick = onClick) {
|
|
||||||
// Text(contentDescription);
|
|
||||||
//// Icon(imageVector = imageVector, contentDescription = contentDescription)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
data class ControlDashboardButtonUiState(val expanded: Boolean, val enabled: Boolean, val visible: Boolean)
|
|
||||||
|
|
||||||
|
|
||||||
data class ControlDashboardUiState(val micState: ControlDashboardButtonUiState) {
|
|
||||||
init { // Check that at most one of the buttons is expanded
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
/* While this template provides a good starting point for using Wear Compose, you can always
|
|
||||||
* take a look at https://github.com/android/wear-os-samples/tree/main/ComposeStarter to find the
|
|
||||||
* most up to date changes to the libraries and their usages.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.birdsounds.identify.presentation
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.ContextWrapper
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import androidx.wear.compose.material.Text
|
|
||||||
import androidx.wear.compose.navigation.SwipeDismissableNavHost
|
|
||||||
import androidx.wear.compose.navigation.composable
|
|
||||||
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
|
|
||||||
import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices
|
|
||||||
import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales
|
|
||||||
import com.birdsounds.identify.presentation.theme.IdentifyTheme
|
|
||||||
import com.google.android.horologist.annotations.ExperimentalHorologistApi
|
|
||||||
import com.google.android.horologist.audio.ui.VolumeViewModel
|
|
||||||
import com.google.android.horologist.compose.layout.ScalingLazyColumn
|
|
||||||
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
|
|
||||||
import com.google.android.horologist.compose.layout.ScreenScaffold
|
|
||||||
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
|
|
||||||
import com.google.android.horologist.compose.material.Chip
|
|
||||||
import com.google.android.horologist.compose.material.ListHeaderDefaults.firstItemPadding
|
|
||||||
import com.google.android.horologist.compose.material.ResponsiveListHeader
|
|
||||||
import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
val flow_stream = MutableStateFlow<String>("")
|
|
||||||
|
|
||||||
val Any.TAG: String
|
|
||||||
get() {
|
|
||||||
val tag = javaClass.simpleName
|
|
||||||
return if (tag.length <= 23) tag else tag.substring(0, 23)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
installSplashScreen()
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setTheme(android.R.style.Theme_DeviceDefault)
|
|
||||||
setContent {
|
|
||||||
WearApp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalHorologistApi::class)
|
|
||||||
@Composable
|
|
||||||
@Preview
|
|
||||||
fun WearApp() {
|
|
||||||
|
|
||||||
IdentifyTheme {
|
|
||||||
lateinit var requestPermissionLauncher: ManagedActivityResultLauncher<String, Boolean>
|
|
||||||
val context = LocalContext.current
|
|
||||||
val activity = context.findActivity()
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
|
|
||||||
val volumeViewModel: VolumeViewModel = viewModel(factory = VolumeViewModel.Factory)
|
|
||||||
|
|
||||||
val mainState = remember(activity) {
|
|
||||||
MainState(activity = activity, requestPermission = {
|
|
||||||
requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
requestPermissionLauncher = rememberLauncherForActivityResult(RequestPermission()) {
|
|
||||||
scope.launch {
|
|
||||||
mainState.permissionResultReturned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val navController: NavHostController = rememberSwipeDismissableNavController()
|
|
||||||
mainState.setNavController(navController);
|
|
||||||
SwipeDismissableNavHost(navController = navController, startDestination = "speaker") {
|
|
||||||
|
|
||||||
composable("speaker") {
|
|
||||||
StartRecordingScreen(
|
|
||||||
context = context,
|
|
||||||
navController = navController,
|
|
||||||
appState = mainState.appState,
|
|
||||||
isPermissionDenied = mainState.isPermissionDenied,
|
|
||||||
onMicClicked = {
|
|
||||||
scope.launch {
|
|
||||||
mainState.onMicClicked()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
composable("species_list") {
|
|
||||||
SpeciesListView(context = context)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// var sdnv = SwipeDismissableNavHost(navController = navController, startDestination = "list") {
|
|
||||||
// composable("speaker") {
|
|
||||||
// StartRecordingScreen(
|
|
||||||
// context = context,
|
|
||||||
// appState = mainState.appState,
|
|
||||||
// isPermissionDenied = mainState.isPermissionDenied,
|
|
||||||
// onMicClicked = {
|
|
||||||
// scope.launch {
|
|
||||||
// mainState.onMicClicked()
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
tailrec fun Context.findActivity(): Activity = when (this) {
|
|
||||||
is Activity -> this
|
|
||||||
is ContextWrapper -> baseContext.findActivity()
|
|
||||||
else -> throw IllegalStateException("findActivity should be called in the context of an Activity")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalHorologistApi::class)
|
|
||||||
@Composable
|
|
||||||
fun MessageDetail(id: String) {
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
|
|
||||||
ScreenScaffold(scrollState = scrollState) {
|
|
||||||
val padding =
|
|
||||||
ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text, last = ScalingLazyColumnDefaults.ItemType.Text)()
|
|
||||||
Column(modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(scrollState)
|
|
||||||
.rotaryWithScroll(scrollState)
|
|
||||||
.padding(padding),
|
|
||||||
verticalArrangement = Arrangement.Center) {
|
|
||||||
Text(text = id, textAlign = TextAlign.Center, modifier = Modifier.fillMaxSize())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalHorologistApi::class)
|
|
||||||
@Composable
|
|
||||||
fun MessageList(onMessageClick: (String) -> Unit) {
|
|
||||||
val columnState =
|
|
||||||
rememberResponsiveColumnState(contentPadding = ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text,
|
|
||||||
last = ScalingLazyColumnDefaults.ItemType.Chip))
|
|
||||||
|
|
||||||
ScreenScaffold(scrollState = columnState) {
|
|
||||||
ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
|
|
||||||
item {
|
|
||||||
ResponsiveListHeader(contentPadding = firstItemPadding()) {
|
|
||||||
Text(text = "Hey hey hey")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Chip(label = "Message 1", onClick = { onMessageClick("message1") })
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Chip(label = "Message 2", onClick = { onMessageClick("message2") })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalHorologistApi::class)
|
|
||||||
@Composable
|
|
||||||
fun MessageList2(onMessageClick: (String) -> Unit) {
|
|
||||||
val columnState =
|
|
||||||
rememberResponsiveColumnState(contentPadding = ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text,
|
|
||||||
last = ScalingLazyColumnDefaults.ItemType.Chip))
|
|
||||||
|
|
||||||
ScreenScaffold(scrollState = columnState) {
|
|
||||||
ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
|
|
||||||
item {
|
|
||||||
ResponsiveListHeader(contentPadding = firstItemPadding()) {
|
|
||||||
Text(text = "Hey hey hey")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Chip(label = "Message 3", onClick = { onMessageClick("message3") })
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Chip(label = "Message 4", onClick = { onMessageClick("message4") })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@WearPreviewDevices
|
|
||||||
@WearPreviewFontScales
|
|
||||||
@Composable
|
|
||||||
fun MessageDetailPreview() {
|
|
||||||
MessageDetail("test")
|
|
||||||
}
|
|
||||||
|
|
||||||
@WearPreviewDevices
|
|
||||||
@WearPreviewFontScales
|
|
||||||
@Composable
|
|
||||||
fun MessageListPreview() {
|
|
||||||
MessageList(onMessageClick = {})
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.birdsounds.identify.presentation
|
|
||||||
|
|
||||||
import com.google.android.gms.wearable.MessageEvent
|
|
||||||
import com.google.android.gms.wearable.WearableListenerService
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
|
|
||||||
class MessageListenerService : WearableListenerService() {
|
|
||||||
private val tag = "MessageListenerService"
|
|
||||||
|
|
||||||
override fun onMessageReceived(p0: MessageEvent) {
|
|
||||||
super.onMessageReceived(p0)
|
|
||||||
val t_scored = ByteBuffer.wrap(p0.data).getLong()
|
|
||||||
var byte_strings: ByteArray = p0.data.copyOfRange(8, p0.data.size)
|
|
||||||
var score_species_string = byte_strings.decodeToString()
|
|
||||||
var list_strings: List<String> = score_species_string.split(';')
|
|
||||||
list_strings.map({
|
|
||||||
var split_str = it.split(',')
|
|
||||||
if (split_str.size == 2) {
|
|
||||||
var out = AScore(split_str[0], split_str[1].toFloat(), t_scored)
|
|
||||||
if (out.score > 0.05) {
|
|
||||||
SpeciesList.add_observation(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
MessageSender.messageLog.add(t_scored)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
package com.birdsounds.identify.presentation
|
|
||||||
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.content.Context
|
|
||||||
import android.media.AudioFormat
|
|
||||||
import android.media.AudioRecord
|
|
||||||
import android.media.MediaRecorder
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.annotation.RequiresPermission
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper class to provide methods to record audio input from the MIC to the internal storage.
|
|
||||||
*/
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
class SoundRecorder(
|
|
||||||
context_in: Context,
|
|
||||||
outputFileName: String
|
|
||||||
) {
|
|
||||||
|
|
||||||
private var state = State.IDLE
|
|
||||||
private var context = context_in
|
|
||||||
|
|
||||||
private enum class State {
|
|
||||||
IDLE, RECORDING
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
|
|
||||||
suspend fun record() {
|
|
||||||
|
|
||||||
|
|
||||||
suspendCancellableCoroutine<Unit> { cont ->
|
|
||||||
var chunk_index: Int = 0
|
|
||||||
val audioSource = MediaRecorder.AudioSource.DEFAULT
|
|
||||||
val sampleRateInHz = 48000
|
|
||||||
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
|
||||||
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
|
||||||
val buffer_size =
|
|
||||||
4 * AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
|
|
||||||
// Log.w(TAG, buffer_size.toString())
|
|
||||||
|
|
||||||
val bufferSizeInBytes =
|
|
||||||
sampleRateInHz * 3 * 2 // 3 second sample, 2 bytes for each sample
|
|
||||||
|
|
||||||
val chunk_size = 2 * sampleRateInHz / 4 // 250ms segments, 2 bytes for each sample
|
|
||||||
val num_chunks: Int = bufferSizeInBytes / chunk_size
|
|
||||||
val chunked_audio_bytes = Array(num_chunks) { ByteArray(chunk_size) }
|
|
||||||
|
|
||||||
val audio_bytes_array = ByteArray(bufferSizeInBytes)
|
|
||||||
val audioRecord = AudioRecord(
|
|
||||||
/* audioSource = */ audioSource,
|
|
||||||
/* sampleRateInHz = */ sampleRateInHz,
|
|
||||||
/* channelConfig = */ channelConfig,
|
|
||||||
/* audioFormat = */ audioFormat,
|
|
||||||
/* bufferSizeInBytes = */ buffer_size
|
|
||||||
)
|
|
||||||
audioRecord.startRecording()
|
|
||||||
|
|
||||||
|
|
||||||
val thread = Thread {
|
|
||||||
// var sent_first: Boolean = false
|
|
||||||
var ignore_warmup: Boolean = true
|
|
||||||
|
|
||||||
var num_chunked_since_last_send = 0
|
|
||||||
var last_tstamp: Long = Instant.now().toEpochMilli()
|
|
||||||
var do_send_message: Boolean = false
|
|
||||||
while (true) {
|
|
||||||
if (Thread.interrupted()) {
|
|
||||||
// check for the interrupted flag, reset it, and throw exception
|
|
||||||
Log.w(TAG, "Finished thread")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
chunk_index = chunk_index.mod(num_chunks)
|
|
||||||
val out = audioRecord.read(
|
|
||||||
/* audioData = */ chunked_audio_bytes[chunk_index],
|
|
||||||
/* offsetInBytes = */ 0,
|
|
||||||
/* sizeInBytes = */ chunk_size,
|
|
||||||
/* readMode = */ AudioRecord.READ_BLOCKING
|
|
||||||
)
|
|
||||||
num_chunked_since_last_send += 1
|
|
||||||
|
|
||||||
do_send_message = false
|
|
||||||
if (num_chunked_since_last_send >= num_chunks) {
|
|
||||||
do_send_message = true
|
|
||||||
Log.w("MSG","sending message because full 3s have passed")
|
|
||||||
} else if ((last_tstamp in MessageSender.messageLog) && (num_chunked_since_last_send>4)) {
|
|
||||||
do_send_message = true
|
|
||||||
Log.w("MSG","Send message because the phone has finished")
|
|
||||||
} else if ((ignore_warmup) && (num_chunked_since_last_send > 2)) {
|
|
||||||
do_send_message = true
|
|
||||||
Log.w("MSG","Sent message because ignoring warmup")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
chunk_index += 1
|
|
||||||
if ((do_send_message)) {
|
|
||||||
var tstamp: Long = Instant.now().toEpochMilli()
|
|
||||||
val tstamp_buffer = ByteBuffer.allocate(Long.SIZE_BYTES)
|
|
||||||
val tstamp_bytes = tstamp_buffer.putLong(tstamp).array()
|
|
||||||
var byte_send: ByteArray = ByteArray(0)
|
|
||||||
var strr: String = ""
|
|
||||||
for (i in 0..(num_chunks - 1)) {
|
|
||||||
var c_index = i + chunk_index
|
|
||||||
c_index = c_index.mod(num_chunks)
|
|
||||||
strr += c_index.toString()
|
|
||||||
strr += ' '
|
|
||||||
byte_send += chunked_audio_bytes[c_index]
|
|
||||||
}
|
|
||||||
// do_send_message = false;
|
|
||||||
num_chunked_since_last_send = 0
|
|
||||||
MessageSender.messageLog.clear()
|
|
||||||
MessageSender.sendMessage("/audio", tstamp_bytes + byte_send, context)
|
|
||||||
last_tstamp = tstamp
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
thread.start()
|
|
||||||
// thread.join();
|
|
||||||
|
|
||||||
cont.invokeOnCancellation {
|
|
||||||
thread.interrupt()
|
|
||||||
audioRecord.stop()
|
|
||||||
audioRecord.release()
|
|
||||||
state = State.IDLE
|
|
||||||
}
|
|
||||||
state = State.IDLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "SoundRecorder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package com.birdsounds.identify.presentation
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
|
|
||||||
class AScore(
|
|
||||||
_species: String,
|
|
||||||
_score: Float,
|
|
||||||
_timestamp: Long,
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
var split_stuff = _species.split("_")
|
|
||||||
val species = split_stuff[0]
|
|
||||||
val score = _score
|
|
||||||
val common_name = split_stuff[1]
|
|
||||||
val timestamp = _timestamp
|
|
||||||
fun age(): Long {
|
|
||||||
var tstamp: Long = Instant.now().toEpochMilli()
|
|
||||||
return (tstamp - timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
var tstamp: Long = Instant.now().toEpochMilli()
|
|
||||||
return common_name + "," + species + "," + score.toString() + ", " + (age() / 1000.0).toString() + "s ago"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
object SpeciesList {
|
|
||||||
var internal_list = mutableListOf<AScore>()
|
|
||||||
var do_add_observation = false
|
|
||||||
var _list_on_ui: SnapshotStateList<MutableState<String>>? = null
|
|
||||||
fun setSpeciecsShow_list(list_in: SnapshotStateList<MutableState<String>>) {
|
|
||||||
_list_on_ui = list_in;
|
|
||||||
}
|
|
||||||
|
|
||||||
fun add_observation(species_in: AScore) {
|
|
||||||
Log.w(TAG,"In add obsergation")
|
|
||||||
do_add_observation = false
|
|
||||||
var idx = 0
|
|
||||||
var idx_replace = -1
|
|
||||||
for (i in internal_list) {
|
|
||||||
if (i.species == species_in.species) {
|
|
||||||
do_add_observation = false
|
|
||||||
idx_replace = idx
|
|
||||||
}
|
|
||||||
idx += 1
|
|
||||||
}
|
|
||||||
if (idx_replace >= 0) {
|
|
||||||
Log.w(TAG, "Replacing")
|
|
||||||
internal_list[idx_replace] = species_in // _list_on_ui?.removeAt(idx_replace)
|
|
||||||
// _list_on_ui?.add(species_in);
|
|
||||||
// if (_list_on_ui != null) {
|
|
||||||
// _list_on_ui?.removeAt(idx_replace)
|
|
||||||
// _list_on_ui.add(idx_replace, species_in);
|
|
||||||
// }
|
|
||||||
|
|
||||||
} else {
|
|
||||||
internal_list.add(species_in)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal_list = internal_list.sortedBy({ (it.age()) }).toMutableList()
|
|
||||||
|
|
||||||
for ((index, value) in internal_list.withIndex()) {
|
|
||||||
_list_on_ui?.get(index)?.value = value.common_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.w(TAG, internal_list.size.toString())
|
|
||||||
Log.w(TAG, _list_on_ui?.size.toString()) // internal_list.add(species_in)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package com.birdsounds.identify.presentation
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.wear.compose.foundation.lazy.items
|
|
||||||
import androidx.wear.compose.material.Text
|
|
||||||
import com.google.android.horologist.annotations.ExperimentalHorologistApi
|
|
||||||
import com.google.android.horologist.compose.layout.ScalingLazyColumn
|
|
||||||
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
|
|
||||||
import com.google.android.horologist.compose.layout.ScreenScaffold
|
|
||||||
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalHorologistApi::class)
|
|
||||||
@Composable
|
|
||||||
fun SpeciesListView(context: Context,
|
|
||||||
) {
|
|
||||||
val text: MutableState<String> = mutableStateOf("text")
|
|
||||||
//text.toString()
|
|
||||||
val species_list_show = mutableStateListOf<MutableState<String>>()
|
|
||||||
for (i in 1..10)
|
|
||||||
{
|
|
||||||
val hi = mutableStateOf("hi")
|
|
||||||
species_list_show.add(hi);
|
|
||||||
}
|
|
||||||
SpeciesList.setSpeciecsShow_list(species_list_show)
|
|
||||||
val species_show: SnapshotStateList<MutableState<String>> = remember { species_list_show }
|
|
||||||
|
|
||||||
val columnState =
|
|
||||||
rememberResponsiveColumnState(contentPadding = ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text,
|
|
||||||
last = ScalingLazyColumnDefaults.ItemType.Chip))
|
|
||||||
|
|
||||||
ScreenScaffold(scrollState = columnState) {
|
|
||||||
ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
|
|
||||||
items(species_show) { aSpec -> Text(text = aSpec.value)
|
|
||||||
} // Dynamically display the chips
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package com.birdsounds.identify.presentation
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import com.google.android.horologist.compose.layout.ScreenScaffold
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StartRecordingScreen(
|
|
||||||
context: Context,
|
|
||||||
appState: AppState,
|
|
||||||
navController: NavHostController,
|
|
||||||
isPermissionDenied: Boolean,
|
|
||||||
onMicClicked: () -> Unit
|
|
||||||
) {
|
|
||||||
ScreenScaffold {
|
|
||||||
val controlDashboardUiState = computeControlDashboardUiState(
|
|
||||||
appState = appState,
|
|
||||||
)
|
|
||||||
ControlDashboard(
|
|
||||||
controlDashboardUiState = controlDashboardUiState,
|
|
||||||
onMicClicked = onMicClicked,
|
|
||||||
navController = navController
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun computeControlDashboardUiState(
|
|
||||||
appState: AppState,
|
|
||||||
): ControlDashboardUiState =
|
|
||||||
when (appState) {
|
|
||||||
AppState.Ready -> ControlDashboardUiState(
|
|
||||||
micState = ControlDashboardButtonUiState(expanded = false, visible = true, enabled = true),
|
|
||||||
)
|
|
||||||
AppState.Recording -> ControlDashboardUiState(
|
|
||||||
micState = ControlDashboardButtonUiState(expanded = true, visible = true, enabled = true),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PlaybackStatePreviewProvider : CollectionPreviewParameterProvider<AppState>(
|
|
||||||
listOf(
|
|
||||||
AppState.Recording,
|
|
||||||
AppState.Ready
|
|
||||||
)
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import android.content.ContentValues.TAG
|
||||||
|
import android.media.MediaCodec
|
||||||
|
import android.media.MediaCodecInfo
|
||||||
|
import android.media.MediaCodecList
|
||||||
|
import android.media.MediaFormat
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes PCM audio data to AAC format
|
||||||
|
* @param pcmData The raw PCM audio data to encode
|
||||||
|
* @param sampleRate Sample rate of the audio (e.g., 44100)
|
||||||
|
* @param channelCount Number of audio channels (1 for mono, 2 for stereo)
|
||||||
|
* @return ByteArray containing the encoded AAC audio data
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
fun listMediaCodecEncoders() {
|
||||||
|
val codecList = MediaCodecList(MediaCodecList.ALL_CODECS) // List all codecs
|
||||||
|
val codecs = codecList.codecInfos
|
||||||
|
|
||||||
|
println("Available MediaCodec Encoders:")
|
||||||
|
for (codec in codecs) {
|
||||||
|
if (codec.isEncoder) {
|
||||||
|
Log.e(TAG, "Encoder: ${codec.name}")
|
||||||
|
// List supported types for this encoder
|
||||||
|
val supportedTypes = codec.supportedTypes
|
||||||
|
Log.w(TAG, " Supported Types:")
|
||||||
|
for (type in supportedTypes) {
|
||||||
|
Log.w(TAG, " $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encodePcmToAac(pcmData: ByteArray): ByteArray {
|
||||||
|
// Create a format for the encoder
|
||||||
|
var sampleRate = 48000
|
||||||
|
var channelCount = 1
|
||||||
|
// listMediaCodecEncoders();
|
||||||
|
val format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_OPUS, sampleRate, channelCount)
|
||||||
|
format.setInteger(MediaFormat.KEY_BIT_RATE, 64000) // 128kbps
|
||||||
|
// format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
|
||||||
|
// format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, pcmData.size)
|
||||||
|
|
||||||
|
// Create and configure the encoder
|
||||||
|
val codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_OPUS)
|
||||||
|
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
|
||||||
|
codec.start()
|
||||||
|
|
||||||
|
// Use ByteArrayOutputStream to collect encoded data
|
||||||
|
val encodedBytes = mutableListOf<Byte>()
|
||||||
|
val bufferInfo = MediaCodec.BufferInfo()
|
||||||
|
|
||||||
|
var allInputSubmitted = false
|
||||||
|
var inputOffset = 0
|
||||||
|
var presentationTimeUs = 0L
|
||||||
|
val frameSize = 1024 * channelCount * 2 // Typical frame size for AAC encoding (1024 samples, 16-bit PCM)
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!allInputSubmitted || bufferInfo.flags != MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
|
||||||
|
// Submit input data to encoder
|
||||||
|
if (!allInputSubmitted) {
|
||||||
|
val inputBufferId = codec.dequeueInputBuffer(10000)
|
||||||
|
if (inputBufferId >= 0) {
|
||||||
|
val inputBuffer = codec.getInputBuffer(inputBufferId)
|
||||||
|
inputBuffer?.clear()
|
||||||
|
|
||||||
|
// Calculate how many bytes to read
|
||||||
|
val bytesToRead = if (inputOffset < pcmData.size) {
|
||||||
|
minOf(inputBuffer!!.capacity(), pcmData.size - inputOffset)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesToRead > 0) {
|
||||||
|
// Copy data from byte array to input buffer
|
||||||
|
inputBuffer!!.put(pcmData, inputOffset, bytesToRead)
|
||||||
|
inputOffset += bytesToRead
|
||||||
|
|
||||||
|
// Calculate presentation time in microseconds
|
||||||
|
// (samples / sample rate) * 1_000_000
|
||||||
|
val samples = bytesToRead / (2 * channelCount) // 16-bit samples
|
||||||
|
presentationTimeUs += samples * 1_000_000L / sampleRate
|
||||||
|
|
||||||
|
codec.queueInputBuffer(
|
||||||
|
inputBufferId,
|
||||||
|
0,
|
||||||
|
bytesToRead,
|
||||||
|
presentationTimeUs,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// End of input data
|
||||||
|
codec.queueInputBuffer(
|
||||||
|
inputBufferId,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
presentationTimeUs,
|
||||||
|
MediaCodec.BUFFER_FLAG_END_OF_STREAM
|
||||||
|
)
|
||||||
|
allInputSubmitted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get encoded data from encoder
|
||||||
|
val outputBufferId = codec.dequeueOutputBuffer(bufferInfo, 10000)
|
||||||
|
when {
|
||||||
|
outputBufferId >= 0 -> {
|
||||||
|
val outputBuffer = codec.getOutputBuffer(outputBufferId)
|
||||||
|
|
||||||
|
if (outputBuffer != null && bufferInfo.size > 0) {
|
||||||
|
// Copy encoded data to our result buffer
|
||||||
|
val encodedChunk = ByteArray(bufferInfo.size)
|
||||||
|
outputBuffer.position(bufferInfo.offset)
|
||||||
|
outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
|
||||||
|
outputBuffer.get(encodedChunk)
|
||||||
|
encodedBytes.addAll(encodedChunk.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
codec.releaseOutputBuffer(outputBufferId, false)
|
||||||
|
|
||||||
|
if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
|
||||||
|
// You might want to store the format for muxing
|
||||||
|
val newFormat = codec.outputFormat
|
||||||
|
// Log.d("MediaCodec", "Output format changed: $newFormat")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
codec.stop()
|
||||||
|
codec.release()
|
||||||
|
}
|
||||||
|
return encodedBytes.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
import androidx.compose.ui.unit.TextUnitType
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.wear.compose.material.Text
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FlashingText(
|
||||||
|
text: String,
|
||||||
|
color: Color,
|
||||||
|
textDecor: TextDecoration = TextDecoration.None,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
key: Long,
|
||||||
|
fontStyle: FontStyle = FontStyle.Normal
|
||||||
|
) {
|
||||||
|
// Set up the flashing state
|
||||||
|
var isFlashing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// Create animation for the background color
|
||||||
|
val backgroundColor by animateColorAsState(
|
||||||
|
targetValue = if (isFlashing) Color.DarkGray else Color.Transparent,
|
||||||
|
animationSpec = tween(durationMillis = 1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Trigger the flash effect once
|
||||||
|
LaunchedEffect(key1 = key) {
|
||||||
|
isFlashing = true
|
||||||
|
delay(1500)
|
||||||
|
isFlashing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the text with animated background
|
||||||
|
AutoSizedText(
|
||||||
|
text = text,
|
||||||
|
color = color,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = modifier
|
||||||
|
.background(color = backgroundColor),
|
||||||
|
textDecor = textDecor,
|
||||||
|
fontStyle = fontStyle
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AutoSizedText(
|
||||||
|
text: String,
|
||||||
|
color: Color = Color.White,
|
||||||
|
textAlign: TextAlign = TextAlign.Center,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
textDecor: TextDecoration = TextDecoration.None,
|
||||||
|
minFontSize: TextUnit = 5.sp,
|
||||||
|
targetFontSize: TextUnit = 24.sp,
|
||||||
|
fontWeight: FontWeight = FontWeight.Normal,
|
||||||
|
fontStyle: FontStyle = FontStyle.Normal,
|
||||||
|
) {
|
||||||
|
var display_text = text;
|
||||||
|
val textMeasurer = rememberTextMeasurer()
|
||||||
|
BoxWithConstraints(modifier) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
// val minFontSizePx = with(density) { minFontSize.toPx() }
|
||||||
|
// val maxFontSizePx = with(density) { targetFontSize.toPx() }
|
||||||
|
val availableWidthPx = with(density) { maxWidth.toPx() }
|
||||||
|
|
||||||
|
// Binary search to find the appropriate font size that fits the available width
|
||||||
|
var lowPx: TextUnit = minFontSize
|
||||||
|
var highPx: TextUnit = targetFontSize
|
||||||
|
var bestFontSizePx = targetFontSize;
|
||||||
|
val textStyle = TextStyle(fontWeight = fontWeight, color = color, fontSize=bestFontSizePx)
|
||||||
|
while (lowPx <= highPx) {
|
||||||
|
val midPx: TextUnit = TextUnit(value=lowPx.value/2 + highPx.value/2 , type= TextUnitType.Sp)
|
||||||
|
val style = textStyle.copy(fontSize = midPx)
|
||||||
|
|
||||||
|
val textLayoutResult = textMeasurer.measure(
|
||||||
|
text = display_text,
|
||||||
|
style = style,
|
||||||
|
maxLines = 1,
|
||||||
|
softWrap = false,
|
||||||
|
density = density,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (textLayoutResult.size.width <= availableWidthPx) {
|
||||||
|
// // This font size fits, try a larger oneb
|
||||||
|
bestFontSizePx = midPx;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// // This font size is too large, try a smaller one
|
||||||
|
// highPx = (midPx - 1).toFloat()
|
||||||
|
highPx = TextUnit(value=midPx.value - 1, type=TextUnitType.Sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the best font size back to sp and use it
|
||||||
|
// val bestFontSize = with(density) { bestFontSizePx.toSp() }
|
||||||
|
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = display_text,
|
||||||
|
color = color,
|
||||||
|
fontSize = bestFontSizePx,
|
||||||
|
fontStyle = fontStyle,
|
||||||
|
fontWeight = fontWeight,
|
||||||
|
maxLines = 1,
|
||||||
|
textDecoration = textDecor,
|
||||||
|
overflow = TextOverflow.Clip,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = textAlign
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
/* While this template provides a good starting point for using Wear Compose, you can always
|
||||||
|
* take a look at https://github.com/android/wear-os-samples/tree/main/ComposeStarter to find the
|
||||||
|
* most up to date changes to the libraries and their usages.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.ServiceCompat.startForeground
|
||||||
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.wear.ambient.AmbientLifecycleObserver
|
||||||
|
import androidx.wear.compose.material.CompactChip
|
||||||
|
import androidx.wear.compose.material.MaterialTheme
|
||||||
|
import androidx.wear.compose.material.Text
|
||||||
|
import androidx.wear.compose.navigation.SwipeDismissableNavHost
|
||||||
|
import androidx.wear.compose.navigation.composable
|
||||||
|
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
|
||||||
|
import androidx.wear.ongoing.OngoingActivity
|
||||||
|
import com.birdsounds.identify.R
|
||||||
|
import com.birdsounds.identify.presentation.theme.IdentifyTheme
|
||||||
|
import com.google.android.horologist.annotations.ExperimentalHorologistApi
|
||||||
|
import com.google.android.horologist.audio.ui.VolumeViewModel
|
||||||
|
import com.google.android.horologist.compose.layout.ScreenScaffold
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
val flow_stream = MutableStateFlow<String>("")
|
||||||
|
|
||||||
|
val Any.TAG: String
|
||||||
|
get() {
|
||||||
|
val tag = javaClass.simpleName
|
||||||
|
return if (tag.length <= 23) tag else tag.substring(0, 23)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
|
||||||
|
lateinit var ambientObserver:AmbientLifecycleObserver
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
installSplashScreen()
|
||||||
|
ambientObserver= AmbientLifecycleObserver(this.findActivity(), ambientCallback)
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
lifecycle.addObserver(ambientObserver)
|
||||||
|
setTheme(android.R.style.Theme_DeviceDefault)
|
||||||
|
setContent {
|
||||||
|
WearApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ForegroundServiceType")
|
||||||
|
@OptIn(ExperimentalHorologistApi::class)
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
fun WearApp() {
|
||||||
|
IdentifyTheme {
|
||||||
|
lateinit var requestPermissionLauncher: ManagedActivityResultLauncher<String, Boolean>
|
||||||
|
val context = LocalContext.current
|
||||||
|
val activity = context.findActivity()
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
|
||||||
|
val touchIntent =
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
Intent(context, MainActivity::class.java),
|
||||||
|
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val notificationBuilder =
|
||||||
|
NotificationCompat.Builder(context, "Identify Watch App")
|
||||||
|
.setOngoing(true)
|
||||||
|
.setSmallIcon(com.birdsounds.identify.R.mipmap.ic_launcher)
|
||||||
|
|
||||||
|
|
||||||
|
val ongoingActivity =
|
||||||
|
OngoingActivity.Builder(
|
||||||
|
context, 4, notificationBuilder
|
||||||
|
).setTouchIntent(touchIntent)
|
||||||
|
.setStaticIcon(
|
||||||
|
com.birdsounds.identify.R.mipmap.ic_launcher)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
|
||||||
|
ongoingActivity.apply(context)
|
||||||
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.notify(
|
||||||
|
4,
|
||||||
|
notificationBuilder.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
val notification: Notification = NotificationCompat.Builder(
|
||||||
|
context, "Identify Watch App"
|
||||||
|
)
|
||||||
|
.setContentTitle("Always On App")
|
||||||
|
.setContentText("Running in foreground")
|
||||||
|
.setSmallIcon(com.birdsounds.identify.R.mipmap.ic_launcher)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val volumeViewModel: VolumeViewModel = viewModel(factory = VolumeViewModel.Factory)
|
||||||
|
|
||||||
|
val mainState = remember(activity) {
|
||||||
|
MainState(activity = activity, requestPermission = {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
requestPermissionLauncher = rememberLauncherForActivityResult(RequestPermission()) {
|
||||||
|
scope.launch {
|
||||||
|
mainState.permissionResultReturned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val navController: NavHostController = rememberSwipeDismissableNavController()
|
||||||
|
mainState.setNavController(navController);
|
||||||
|
SwipeDismissableNavHost(
|
||||||
|
navController = navController,
|
||||||
|
userSwipeEnabled = true,
|
||||||
|
startDestination = "speaker"
|
||||||
|
) {
|
||||||
|
composable("species_list") {
|
||||||
|
ScreenScaffold {
|
||||||
|
SpeciesListView(context = context, appState = mainState.appState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
composable("speaker") {
|
||||||
|
ScreenScaffold {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.align(Alignment.Center)
|
||||||
|
) {
|
||||||
|
Row() {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
// Define the animation for the scale
|
||||||
|
val scale by animateFloatAsState(
|
||||||
|
targetValue = if (isPressed) 2.0f else 1.0f
|
||||||
|
)
|
||||||
|
|
||||||
|
CompactChip(
|
||||||
|
onClick = {
|
||||||
|
vibrate(
|
||||||
|
context,
|
||||||
|
250
|
||||||
|
);navController.navigate("species_list")
|
||||||
|
},
|
||||||
|
enabled = true,
|
||||||
|
modifier = Modifier.scale(scale),
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
label = {
|
||||||
|
Text(text = "Show List")
|
||||||
|
},
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
Row() {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
// Define the animation for the scale
|
||||||
|
val scale by animateFloatAsState(
|
||||||
|
targetValue = if (isPressed) 2.0f else 1.0f
|
||||||
|
)
|
||||||
|
CompactChip(
|
||||||
|
onClick = {
|
||||||
|
vibrate(context, 250);
|
||||||
|
scope.launch {
|
||||||
|
mainState.onMicClicked()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.scale(scale),
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
enabled = true,
|
||||||
|
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
label = {
|
||||||
|
Text(text = if (mainState.appState == AppState.Recording) "Stop Recording" else "Start Recording")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Row() {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
// Define the animation for the scale
|
||||||
|
val scale by animateFloatAsState(
|
||||||
|
targetValue = if (isPressed) 2.0f else 1.0f
|
||||||
|
)
|
||||||
|
CompactChip(
|
||||||
|
onClick = {
|
||||||
|
vibrate(context, 250);
|
||||||
|
species_list_show.clear();
|
||||||
|
internal_list.clear();
|
||||||
|
},
|
||||||
|
enabled = true,
|
||||||
|
modifier = Modifier.scale(scale),
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
label = {
|
||||||
|
Text(text = "Clear List")
|
||||||
|
},
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tailrec fun Context.findActivity(): Activity = when (this) {
|
||||||
|
is Activity -> this
|
||||||
|
is ContextWrapper -> baseContext.findActivity()
|
||||||
|
else -> throw IllegalStateException("findActivity should be called in the context of an Activity")
|
||||||
|
}
|
||||||
|
|
||||||
@@ -31,6 +31,9 @@ class MainState(private val activity: Activity, private val requestPermission: (
|
|||||||
public fun setNavController(_navController: NavHostController) {
|
public fun setNavController(_navController: NavHostController) {
|
||||||
navController = _navController
|
navController = _navController
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
suspend fun onMicClicked() {
|
suspend fun onMicClicked() {
|
||||||
playbackStateMutatorMutex.mutate {
|
playbackStateMutatorMutex.mutate {
|
||||||
when (appState) {
|
when (appState) {
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import com.google.android.gms.wearable.MessageEvent
|
||||||
|
import com.google.android.gms.wearable.WearableListenerService
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
var last_message_recv_tstamp: Long = 0L;
|
||||||
|
|
||||||
|
class MessageListenerService : WearableListenerService() {
|
||||||
|
private val tag = "MessageListenerService"
|
||||||
|
lateinit var this_context: Context;
|
||||||
|
fun set_context(context: Context)
|
||||||
|
{
|
||||||
|
this_context = context;
|
||||||
|
}
|
||||||
|
override fun onMessageReceived(p0: MessageEvent) {
|
||||||
|
super.onMessageReceived(p0)
|
||||||
|
val t_scored = ByteBuffer.wrap(p0.data).getLong()
|
||||||
|
last_message_recv_tstamp = t_scored;
|
||||||
|
|
||||||
|
var byte_strings: ByteArray = p0.data.copyOfRange(8, p0.data.size)
|
||||||
|
var score_species_string = byte_strings.decodeToString()
|
||||||
|
var list_strings: List<String> = score_species_string.split(';')
|
||||||
|
var do_vibrate = false;
|
||||||
|
var did_add = false;
|
||||||
|
var new_entries = 0;
|
||||||
|
SpeciesList.clear_new_flags();
|
||||||
|
list_strings.map({
|
||||||
|
var split_str = it.split(',')
|
||||||
|
if (split_str.size == 2) {
|
||||||
|
new_entries+=1;
|
||||||
|
var out = AScore(split_str[0], split_str[1].toFloat(), t_scored);
|
||||||
|
out.new_entry = true;
|
||||||
|
|
||||||
|
if (SpeciesList.add_observation(out))
|
||||||
|
{
|
||||||
|
did_add = true;
|
||||||
|
}
|
||||||
|
do_vibrate = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// SpeciesList.set_num_new_entries(new_entries);
|
||||||
|
// SpeciesList.insert_new_entry_spacer(new_entries);
|
||||||
|
if (did_add) {
|
||||||
|
vibrateDouble(this, 100, 250, 100);
|
||||||
|
} else if (do_vibrate) {
|
||||||
|
vibrate(this,100)
|
||||||
|
}
|
||||||
|
// if (new_entries > 0) {
|
||||||
|
SpeciesList.update_list_on_ui_thread();
|
||||||
|
// }
|
||||||
|
|
||||||
|
MessageSender.messageLog.add(t_scored)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,24 +10,32 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
import java.util.zip.GZIPOutputStream
|
||||||
|
|
||||||
|
|
||||||
object MessageSender {
|
object MessageSender {
|
||||||
const val tag = "MessageSender"
|
const val tag = "MessageSender"
|
||||||
|
|
||||||
private val job = Job()
|
private val job = Job()
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.IO + job)
|
private val coroutineScope = CoroutineScope(Dispatchers.IO + job)
|
||||||
var messageLog = ConcurrentMutableSet<Long>()
|
var messageLog = ConcurrentMutableSet<Long>()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun sendMessage(path: String, message: ByteArray, context: Context) {
|
fun sendMessage(path: String, message: ByteArray, context: Context) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
sendMessageInBackground(path, message, context)
|
sendMessageInBackground(path, message, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun compressByteArray(input: ByteArray): ByteArray {
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
GZIPOutputStream(outputStream).use { gzip ->
|
||||||
|
gzip.write(input) // Compress the byte array
|
||||||
|
}
|
||||||
|
return outputStream.toByteArray() // Return the compressed data
|
||||||
|
}
|
||||||
private fun sendMessageInBackground(path: String, message: ByteArray, context: Context) {
|
private fun sendMessageInBackground(path: String, message: ByteArray, context: Context) {
|
||||||
//first get all the nodes, ie connected wearable devices.
|
//first get all the nodes, ie connected wearable devices.
|
||||||
val nodeListTask = Wearable.getNodeClient(context).connectedNodes
|
val nodeListTask = Wearable.getNodeClient(context).connectedNodes
|
||||||
@@ -38,6 +46,19 @@ object MessageSender {
|
|||||||
if(nodes.isEmpty()) {
|
if(nodes.isEmpty()) {
|
||||||
Log.i(tag,"No Node found to send message")
|
Log.i(tag,"No Node found to send message")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// var compressed_message = audio_encoder.encodePCMToAAC(message)
|
||||||
|
|
||||||
|
// Log.w(tag, "Uncompressed message size "+message.size.toString())
|
||||||
|
// Log.w(tag, "Compressed message size "+compressed_message.size.toString())
|
||||||
//Now send the message to each device.
|
//Now send the message to each device.
|
||||||
for (node in nodes) {
|
for (node in nodes) {
|
||||||
val sendMessageTask = Wearable.getMessageClient(context)
|
val sendMessageTask = Wearable.getMessageClient(context)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
|
import androidx.wear.ambient.AmbientLifecycleObserver
|
||||||
|
|
||||||
|
val ambientCallback = object : AmbientLifecycleObserver.AmbientLifecycleCallback {
|
||||||
|
override fun onEnterAmbient(ambientDetails: AmbientLifecycleObserver.AmbientDetails) {
|
||||||
|
// ... Called when moving from interactive mode into ambient mode.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onExitAmbient() {
|
||||||
|
// ... Called when leaving ambient mode, back into interactive mode.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUpdateAmbient() {
|
||||||
|
// ... Called by the system in order to allow the app to periodically
|
||||||
|
// update the display while in ambient mode. Typically the system will
|
||||||
|
// call this every 60 seconds.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
|
import com.theeasiestway.opus.Constants
|
||||||
|
import com.theeasiestway.opus.Opus
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
import android.media.AudioFormat
|
||||||
|
import android.media.AudioRecord
|
||||||
|
import android.media.MediaRecorder
|
||||||
|
import android.media.audiofx.AutomaticGainControl
|
||||||
|
import android.media.audiofx.NoiseSuppressor
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.RequiresPermission
|
||||||
|
import encodePcmToAac
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class to provide methods to record audio input from the MIC to the internal storage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
import android.os.Vibrator
|
||||||
|
import android.os.VibratorManager
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
fun vibrateDouble(
|
||||||
|
context: Context,
|
||||||
|
firstDuration: Long = 100,
|
||||||
|
pauseDuration: Long = 200,
|
||||||
|
secondDuration: Long = 100
|
||||||
|
) {
|
||||||
|
// Get the vibrator service
|
||||||
|
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||||
|
vibratorManager.defaultVibrator
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a pattern for double vibration: 0ms delay, firstDuration vibrate, pauseDuration pause, secondDuration vibrate
|
||||||
|
val vibrationPattern = longArrayOf(0, firstDuration, pauseDuration, secondDuration)
|
||||||
|
|
||||||
|
// The -1 parameter means don't repeat the pattern
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val vibrationEffect = VibrationEffect.createWaveform(vibrationPattern, -1)
|
||||||
|
vibrator.vibrate(vibrationEffect)
|
||||||
|
} else {
|
||||||
|
// Deprecated method for older API levels
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
vibrator.vibrate(vibrationPattern, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun vibrate(context: Context, duration: Long = 500) {
|
||||||
|
val vibrator = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||||
|
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||||
|
vibratorManager.defaultVibrator
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
|
}
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||||
|
val vibrationEffect = VibrationEffect.createOneShot(
|
||||||
|
duration, // duration in milliseconds
|
||||||
|
VibrationEffect.DEFAULT_AMPLITUDE // strength of vibration
|
||||||
|
)
|
||||||
|
vibrator.vibrate(vibrationEffect)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
vibrator.vibrate(duration) // Vibration for 500ms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
class SoundRecorder(
|
||||||
|
context_in: Context,
|
||||||
|
outputFileName: String
|
||||||
|
) {
|
||||||
|
|
||||||
|
val codec_opus = Opus();
|
||||||
|
private var state = State.IDLE
|
||||||
|
private var context = context_in
|
||||||
|
|
||||||
|
private enum class State {
|
||||||
|
IDLE, RECORDING
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
|
||||||
|
suspend fun record() {
|
||||||
|
|
||||||
|
|
||||||
|
suspendCancellableCoroutine<Unit> { cont ->
|
||||||
|
var noiseSuppressor: NoiseSuppressor? = null
|
||||||
|
var automaticGainControl: AutomaticGainControl? = null
|
||||||
|
var chunk_index: Int = 0
|
||||||
|
val audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION
|
||||||
|
val sampleRateInHz = 48000
|
||||||
|
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
||||||
|
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
||||||
|
|
||||||
|
val frameSize = Constants.FrameSize._120();
|
||||||
|
val chunkSize = frameSize.v * 2; // Mono * 2 bytes per sample
|
||||||
|
val counts_until_reset = 3 * sampleRateInHz / frameSize.v;
|
||||||
|
val bufferSize =
|
||||||
|
AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
|
||||||
|
|
||||||
|
val audioRecord = AudioRecord(
|
||||||
|
/* audioSource = */ audioSource,
|
||||||
|
/* sampleRateInHz = */ sampleRateInHz,
|
||||||
|
/* channelConfig = */ channelConfig,
|
||||||
|
/* audioFormat = */ audioFormat,
|
||||||
|
/* bufferSizeInBytes = */ bufferSize
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val thread = Thread {
|
||||||
|
// var sent_first: Boolean = false
|
||||||
|
val outputByteStream = ByteBuffer.allocate(1000000)
|
||||||
|
audioRecord.startRecording()
|
||||||
|
var last_tstamp: Long = Instant.now().toEpochMilli();
|
||||||
|
var count: Int = 0;
|
||||||
|
while (true) /**/ {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
audioRecord.release();
|
||||||
|
Log.w(TAG, "Finished thread")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
codec_opus.encoderInit(
|
||||||
|
Constants.SampleRate._48000(),
|
||||||
|
Constants.Channels.mono(),
|
||||||
|
Constants.Application.audio()
|
||||||
|
);
|
||||||
|
outputByteStream.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
val frame = ByteArray(chunkSize)
|
||||||
|
var offset = 0
|
||||||
|
var remained = frame.size
|
||||||
|
while (remained > 0) {
|
||||||
|
val read = audioRecord.read(frame, offset, remained)
|
||||||
|
offset += read
|
||||||
|
remained -= read
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val encoded: ByteArray? =
|
||||||
|
codec_opus.encode(bytes = frame, frameSize = frameSize);
|
||||||
|
if (encoded != null) {
|
||||||
|
outputByteStream.put(encoded.size.toByte());
|
||||||
|
outputByteStream.put(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
if (count == counts_until_reset) {
|
||||||
|
Log.d(TAG, "At count reset at ${count}");
|
||||||
|
codec_opus.encoderRelease();
|
||||||
|
count = 0;
|
||||||
|
val writtenBytes = outputByteStream.position()
|
||||||
|
Log.e(TAG,"Wrote ${writtenBytes} bytes!")
|
||||||
|
val duplicate = outputByteStream.duplicate()
|
||||||
|
duplicate.flip() // Prepare for reading
|
||||||
|
// outputByteStream.flip()
|
||||||
|
val bytes = ByteArray(writtenBytes)
|
||||||
|
duplicate.get(bytes)
|
||||||
|
// val result = ByteArray(outputByteStream.remaining());
|
||||||
|
var tstamp: Long = Instant.now().toEpochMilli()
|
||||||
|
val tstamp_buffer = ByteBuffer.allocate(Long.SIZE_BYTES)
|
||||||
|
val tstamp_bytes = tstamp_buffer.putLong(tstamp).array()
|
||||||
|
var byte_send: ByteArray = tstamp_bytes + bytes;
|
||||||
|
Log.e(TAG, "Sending message of 3s with size ${byte_send.size} + ${byte_send[0].toString()}")
|
||||||
|
MessageSender.sendMessage("/audio", byte_send, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
thread.start()
|
||||||
|
// thread.join();
|
||||||
|
|
||||||
|
cont.invokeOnCancellation {
|
||||||
|
thread.interrupt()
|
||||||
|
audioRecord.stop()
|
||||||
|
audioRecord.release()
|
||||||
|
state = State.IDLE
|
||||||
|
}
|
||||||
|
state = State.IDLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "SoundRecorder"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
|
||||||
|
class AScore(
|
||||||
|
_species: String,
|
||||||
|
_score: Float,
|
||||||
|
_timestamp: Long,
|
||||||
|
_trigger: Boolean = false,
|
||||||
|
_new_entry: Boolean = false,
|
||||||
|
_redraw_number: Long = 0,
|
||||||
|
) {
|
||||||
|
var redraw_number = _redraw_number;
|
||||||
|
val split_stuff: List<String> = _species.split("_");
|
||||||
|
val species = split_stuff[0];
|
||||||
|
val score = _score;
|
||||||
|
var trigger = _trigger;
|
||||||
|
var new_entry = _new_entry;
|
||||||
|
|
||||||
|
// var common_name = split_stuff[1];
|
||||||
|
val common_name = if (split_stuff.size > 1) split_stuff[1] else "";
|
||||||
|
val timestamp = _timestamp
|
||||||
|
fun set_redraw_number(redraw_num: Long)
|
||||||
|
{
|
||||||
|
redraw_number = redraw_num
|
||||||
|
}
|
||||||
|
fun age(): Long {
|
||||||
|
var tstamp: Long = Instant.now().toEpochMilli()
|
||||||
|
return (tstamp - timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
var tstamp: Long = Instant.now().toEpochMilli()
|
||||||
|
return common_name + "," + species + "," + score.toString() + ", " + (age() / 1000.0).toString() + "s ago"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var internal_list = mutableListOf<AScore>()
|
||||||
|
|
||||||
|
object SpeciesList {
|
||||||
|
var trigger_redraw = 0;
|
||||||
|
var num_new_entries = 0;
|
||||||
|
var do_add_observation = false
|
||||||
|
var _list_on_ui: SnapshotStateList<MutableState<AScore>>? = null
|
||||||
|
fun setSpeciecsShow_list(list_in: SnapshotStateList<MutableState<AScore>>) {
|
||||||
|
_list_on_ui = list_in;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear_new_flags() {
|
||||||
|
for (i in internal_list) {
|
||||||
|
i.new_entry = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add_observation(species_in: AScore): Boolean {
|
||||||
|
Log.w(TAG, "In add observation")
|
||||||
|
var idx = 0
|
||||||
|
var idx_replace = -1
|
||||||
|
var added_new = false;
|
||||||
|
for (i in internal_list) {
|
||||||
|
if (i.species == species_in.species) {
|
||||||
|
idx_replace = idx
|
||||||
|
}
|
||||||
|
idx += 1
|
||||||
|
}
|
||||||
|
if (idx_replace >= 0) {
|
||||||
|
Log.w(TAG, "Replacing")
|
||||||
|
internal_list[idx_replace] = species_in
|
||||||
|
internal_list[idx_replace].trigger = true;
|
||||||
|
} else {
|
||||||
|
internal_list.add(species_in)
|
||||||
|
added_new = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal_list = internal_list.sortedBy({ (it.age()) }).toMutableList()
|
||||||
|
internal_list =
|
||||||
|
internal_list.sortedWith(compareBy({ it.age() }, { it.score })).toMutableList()
|
||||||
|
|
||||||
|
return added_new
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun update_list_on_ui_thread(
|
||||||
|
) {
|
||||||
|
while (_list_on_ui!!.size < internal_list.size) {
|
||||||
|
_list_on_ui!!.add(mutableStateOf(AScore("", 0.0F, 0L)))
|
||||||
|
}
|
||||||
|
val time_stamp = System.currentTimeMillis()
|
||||||
|
for ((index, value) in internal_list.withIndex()) {
|
||||||
|
_list_on_ui?.get(index)?.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.format.DateFormat
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Mic
|
||||||
|
import androidx.compose.material.icons.rounded.MicOff
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.key
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.wear.compose.foundation.curvedComposable
|
||||||
|
import androidx.wear.compose.foundation.lazy.items
|
||||||
|
import androidx.wear.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.wear.compose.material.Icon
|
||||||
|
import androidx.wear.compose.material.TimeTextDefaults
|
||||||
|
import androidx.wear.compose.material.TimeText
|
||||||
|
import androidx.wear.compose.material.curvedText
|
||||||
|
import com.google.android.horologist.annotations.ExperimentalHorologistApi
|
||||||
|
import com.google.android.horologist.compose.layout.ScalingLazyColumn
|
||||||
|
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
|
||||||
|
import com.google.android.horologist.compose.layout.ScreenScaffold
|
||||||
|
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
fun interpolateColor(value: Float): Color {
|
||||||
|
// Ensure that the input value is clamped between 0 and 1
|
||||||
|
val clampedValue = value.coerceIn(0f, 1f)
|
||||||
|
|
||||||
|
// Red component: starts at 255 (white) and decreases to 0
|
||||||
|
val red = (255 * (1 - clampedValue)).toInt()
|
||||||
|
|
||||||
|
// Green component: stays at 255 (since both white and green have full green)
|
||||||
|
val green = 255
|
||||||
|
|
||||||
|
// Blue component: starts at 255 (white) and decreases to 0
|
||||||
|
val blue = (255 * (1 - clampedValue)).toInt()
|
||||||
|
|
||||||
|
// Construct the color
|
||||||
|
return Color(red.toInt(), green.toInt(), blue.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val species_list_show = mutableStateListOf<MutableState<AScore>>()
|
||||||
|
|
||||||
|
@SuppressLint("UnrememberedMutableState")
|
||||||
|
@OptIn(ExperimentalHorologistApi::class)
|
||||||
|
@Composable
|
||||||
|
fun SpeciesListView(
|
||||||
|
context: Context, appState: AppState,
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
for (i in 1..3) {
|
||||||
|
val hi = mutableStateOf(AScore("", 0.0F, 0L))
|
||||||
|
species_list_show.add(hi);
|
||||||
|
}
|
||||||
|
SpeciesList.setSpeciecsShow_list(species_list_show)
|
||||||
|
|
||||||
|
val species_show: SnapshotStateList<MutableState<AScore>> = remember { species_list_show }
|
||||||
|
|
||||||
|
// var sP = scalingParams( maxTransitionArea= 0.25f, minTransitionArea = 0.05f)
|
||||||
|
// val columnState = ScalingLazyColumnState(scalingParams = sP);
|
||||||
|
var columnState = rememberResponsiveColumnState(
|
||||||
|
contentPadding = ScalingLazyColumnDefaults.padding(
|
||||||
|
first = ScalingLazyColumnDefaults.ItemType.Text,
|
||||||
|
last = ScalingLazyColumnDefaults.ItemType.Chip
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(
|
||||||
|
space = 1.dp,
|
||||||
|
alignment = Alignment.Top,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val text_counter = remember { mutableStateOf("Initial text") }
|
||||||
|
LaunchedEffect(key1 = true) {
|
||||||
|
while (true) {
|
||||||
|
// Update the text - you can put your custom logic here
|
||||||
|
// For example, showing current time:
|
||||||
|
var lag = ((Instant.now().toEpochMilli() - last_message_recv_tstamp) / 1000F).toInt();
|
||||||
|
if (last_message_recv_tstamp == 0L) {
|
||||||
|
text_counter.value = "No msg recv"
|
||||||
|
} else {
|
||||||
|
text_counter.value = "${lag}s"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay for 1 second
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ScreenScaffold(
|
||||||
|
scrollState = columnState
|
||||||
|
) {
|
||||||
|
TimeText(
|
||||||
|
timeSource =
|
||||||
|
TimeTextDefaults.timeSource(
|
||||||
|
DateFormat.getBestDateTimePattern(Locale.getDefault(), "hh:mm")
|
||||||
|
),
|
||||||
|
startCurvedContent = {
|
||||||
|
curvedComposable {
|
||||||
|
Icon(
|
||||||
|
if (appState == AppState.Recording) Icons.Rounded.Mic else Icons.Rounded.MicOff,
|
||||||
|
contentDescription = "",
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
// tint = androidx.compose.ui.graphics.Color.Red
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// curvedText(
|
||||||
|
// text = if (appState == AppState.Recording) "" else "Inactive0",
|
||||||
|
//// textAlign = TextAlign.Center,
|
||||||
|
// )
|
||||||
|
},
|
||||||
|
endCurvedContent = {
|
||||||
|
|
||||||
|
curvedText(text = text_counter.value)//, textAlign = TextAlign.Center)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.align(Alignment.Center)
|
||||||
|
) {
|
||||||
|
|
||||||
|
ScalingLazyColumn(
|
||||||
|
columnState = columnState,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
itemsIndexed(
|
||||||
|
species_show,
|
||||||
|
) { index, aSpec ->
|
||||||
|
var text_decor = TextDecoration.None
|
||||||
|
var text_style = FontStyle.Normal;
|
||||||
|
|
||||||
|
val currentName = aSpec.value.common_name
|
||||||
|
val currentScore = aSpec.value.score
|
||||||
|
val isNewEntry = aSpec.value.new_entry
|
||||||
|
val shouldTrigger = aSpec.value.trigger
|
||||||
|
|
||||||
|
var add_sep = false;
|
||||||
|
if (aSpec.value.new_entry) {
|
||||||
|
// add_sep = true;
|
||||||
|
text_style = FontStyle.Italic;
|
||||||
|
// aSpec.value.new_entry = false
|
||||||
|
}
|
||||||
|
Log.w(TAG, "Species " + currentName + " " + aSpec.value.new_entry.toString())
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(100);
|
||||||
|
aSpec.value.new_entry = false
|
||||||
|
}
|
||||||
|
// if (index == (SpeciesList.num_new_entries-1)) {
|
||||||
|
// add_sep = true;"
|
||||||
|
// }
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"Adding Separator: " + index.toString() + " " + SpeciesList.num_new_entries + " " + add_sep.toString()
|
||||||
|
)
|
||||||
|
aSpec.value.new_entry = false;
|
||||||
|
|
||||||
|
// var composables: List<@Composable () -> Unit> = listOf();
|
||||||
|
|
||||||
|
key(currentName, isNewEntry, shouldTrigger) {
|
||||||
|
if (shouldTrigger) {
|
||||||
|
Log.w(TAG, "Trigger " + aSpec.toString())
|
||||||
|
FlashingText(
|
||||||
|
text = aSpec.value.common_name,
|
||||||
|
fontStyle = text_style,
|
||||||
|
color = interpolateColor(aSpec.value.score),
|
||||||
|
key = Instant.now().toEpochMilli(),
|
||||||
|
textDecor = text_decor,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(100);
|
||||||
|
aSpec.value.trigger = false
|
||||||
|
}
|
||||||
|
Log.e("TAG", "BLINKING")
|
||||||
|
} else {
|
||||||
|
|
||||||
|
AutoSizedText(
|
||||||
|
text = aSpec.value.common_name,
|
||||||
|
color = interpolateColor(aSpec.value.score),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
textDecor = text_decor,
|
||||||
|
fontStyle = text_style,
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.e("TAG", "Not blinking")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add_sep) {
|
||||||
|
// composables += {
|
||||||
|
AutoSizedText(
|
||||||
|
text = "-------------------",
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column {
|
||||||
|
// composables.forEach { composable ->
|
||||||
|
// Row {
|
||||||
|
// composable()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
}// Dynamically display the chips
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ScreenScaffold(scrollState = columnState) {
|
||||||
|
// ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
|
||||||
|
// items(species_show) { aSpec -> Text(text = aSpec.value.common_name)
|
||||||
|
// } // Dynamically display the chips
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
74
wear/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
android:height="108dp"
|
||||||
|
android:width="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
</vector>
|
||||||
5
wear/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 5.3 KiB |
BIN
wear/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
wear/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 2.9 KiB |
BIN
wear/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
wear/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 8.5 KiB |
BIN
wear/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
wear/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 17 KiB |
BIN
wear/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
wear/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 28 KiB |
BIN
wear/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
wear/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 33 KiB |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<style name="MainActivityTheme.Starting" parent="Theme.SplashScreen">
|
<style name="MainActivityTheme.Starting" parent="Theme.SplashScreen">
|
||||||
<item name="windowSplashScreenBackground">@android:color/black</item>
|
<item name="windowSplashScreenBackground">@android:color/black</item>
|
||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
|
||||||
<item name="postSplashScreenTheme">@android:style/Theme.DeviceDefault</item>
|
<item name="postSplashScreenTheme">@android:style/Theme.DeviceDefault</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||