Restore latest local work after Seafile git corruption repair

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TvDdasWXdhrvQriBCPfFn1
This commit is contained in:
2026-08-30 22:19:01 -04:00
parent e73d52d221
commit a70b507c18
29 changed files with 1089 additions and 1163 deletions
-15
View File
@@ -1,15 +0,0 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
Generated
-1
View File
@@ -1 +0,0 @@
identify
@@ -1,29 +0,0 @@
<?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>
+4 -4
View File
@@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="mobile">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-03-09T15:35:16.375287800Z">
<DropdownSelection timestamp="2025-03-30T04:58:13.296203100Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=RFAX803LMFR" />
<DeviceId pluginId="PhysicalDevice" identifier="serial=569cc5b0" />
</handle>
</Target>
</DropdownSelection>
@@ -15,10 +15,10 @@
</SelectionState>
<SelectionState runConfigName="wear">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-03-29T14:49:45.062493500Z">
<DropdownSelection timestamp="2025-04-24T15:34:17.184421300Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\thebears\.android\avd\Wear_OS_Large_Round_API_34.avd" />
<DeviceId pluginId="Default" identifier="serial=192.168.1.175:37625;connection=a5bdadc2" />
</handle>
</Target>
</DropdownSelection>
@@ -1,260 +0,0 @@
<?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">{
&quot;associatedIndex&quot;: 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>
@@ -1,254 +0,0 @@
<?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">{
&quot;associatedIndex&quot;: 0
}</component>
<component name="ProjectId" id="2toSwb3riPZMH7ufWxT9aBYCKaO" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;Android App.mobile.executor&quot;: &quot;Run&quot;,
&quot;Android App.wear.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;RunOnceActivity.readMode.enableVisualFormatting&quot;: &quot;true&quot;,
&quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;com.google.services.firebase.aqiPopupShown&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/isp/Seafile/Designs/Android/bird_sound_identify_wearos&quot;,
&quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
&quot;project.structure.proportion&quot;: &quot;0.17&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;
}
}</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,254 +0,0 @@
<?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">{
&quot;associatedIndex&quot;: 0
}</component>
<component name="ProjectId" id="2toSwb3riPZMH7ufWxT9aBYCKaO" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;Android App.mobile.executor&quot;: &quot;Run&quot;,
&quot;Android App.wear.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;RunOnceActivity.readMode.enableVisualFormatting&quot;: &quot;true&quot;,
&quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;com.google.services.firebase.aqiPopupShown&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/isp/Seafile/Designs/Android/bird_sound_identify_wearos&quot;,
&quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
&quot;project.structure.proportion&quot;: &quot;0.17&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;
}
}</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>
+4
View File
@@ -40,6 +40,8 @@ runtimeAndroid = "1.6.6"
datastoreCoreAndroid = "1.1.3"
coreKtxVersion = "1.13.0"
animationCoreAndroid = "1.6.6"
#litertSupportApi = "1.2.0"
#litert = "1.2.0"
#litert = "1.0.1"
[libraries]
@@ -91,6 +93,8 @@ core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtxV
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-support-api = { group = "com.google.ai.edge.litert", name = "litert-support-api", version.ref = "litertSupportApi" }
#litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "litert" }
[plugins]
@@ -1,73 +0,0 @@
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"))
}
+9 -1
View File
@@ -51,13 +51,19 @@ dependencies {
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.work.runtime.ktx)
// implementation("org.tensorflow:tensorflow-lite-task-vision")
// implementation("org.tensorflow:tensorflow-lite-gpu-delegate-plugin")
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation("io.github.nailik:androidresampler:0.1")
// implementation("org.tensorflow:tensorflow-lite:1.12.0")
implementation("org.tensorflow:tensorflow-lite:2.16.1")
implementation("org.tensorflow:tensorflow-lite-task-vision:0.4.4")
implementation("androidx.datastore:datastore-preferences:1.1.3")
implementation("org.tensorflow:tensorflow-lite:2.6.0")
// 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(libs.androidx.ui.graphics)
@@ -65,6 +71,8 @@ dependencies {
implementation(libs.androidx.material3)
implementation(libs.androidx.datastore.core.android)
implementation(libs.core.ktx)
// implementation(libs.litert.support.api)
// implementation(libs.litert)
// implementation(libs.litert)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
Binary file not shown.
@@ -42,11 +42,13 @@ class Downloader(mainActivity: MainActivity) {
}
}
fun prepareModelFiles()
{
copyAssetToFolder(settings.pkg_model_file, settings.local_model_file);
copyAssetToFolder(settings.pkg_meta_model_file, settings.local_meta_model_file);
fun prepareModelFiles() {
for (files_to_copy in Settings.all_files) {
copyAssetToFolder(
files_to_copy["pkg"].toString(),
files_to_copy["file"].toString()
)
}
}
}
@@ -31,7 +31,11 @@ object Location {
locationListenerGPS = null
}
fun requestLocation(context: Context, soundClassifier: SoundClassifier) {
fun requestLocation(
context: Context,
soundClassifier: SoundClassifier,
soundClassifierSpectrogramBased: SoundClassifierSpectrogramBased
) {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
@@ -44,6 +48,8 @@ object Location {
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);
@@ -51,11 +57,19 @@ object Location {
Log.w(TAG, "Sound classifier is ready");
soundClassifier.runMetaInterpreter(location)
Log.w(TAG, "Got location changed");
while (!soundClassifierSpectrogramBased.is_model_ready()) {
Thread.sleep(50);
}
Log.w(TAG, "Sound classifier is ready");
soundClassifierSpectrogramBased.runMetaInterpreter(location)
val activity = context as? Activity;
activity?.let {
val text_species: TextView = it.findViewById(R.id.local_species)
text_species.text = local_species;
text_species.text = local_species_spectrogram;
val loc_lon: TextView = it.findViewById(R.id.location_long)
loc_lon.text =
@@ -71,12 +85,7 @@ object Location {
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())
}
}
@@ -4,9 +4,10 @@ import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.os.Bundle
import android.preference.PreferenceManager
import android.util.Log
import android.widget.ImageView
import android.widget.SeekBar
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
@@ -22,11 +23,13 @@ import androidx.datastore.preferences.preferencesDataStore
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.wearable.ChannelClient
import com.google.android.gms.wearable.Wearable
import io.github.nailik.androidresampler.Resampler
import io.github.nailik.androidresampler.ResamplerConfiguration
import io.github.nailik.androidresampler.data.ResamplerChannel
import io.github.nailik.androidresampler.data.ResamplerQuality
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
@@ -36,8 +39,6 @@ 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
@@ -49,30 +50,64 @@ class SynchronousDataStore(private val context: Context) {
// Define keys
companion object {
val THRESHOLD_KEY = intPreferencesKey("user_age")
val THRESHOLD_SPECTROGRAM_KEY = intPreferencesKey("THRESHOLD_SPECTROGRAM")
val THRESHOLD_WAVEFORM_KEY = intPreferencesKey("THRESHOLD_WAVEFORM")
val THRESHOLD_BIRDHEARD_KEY = intPreferencesKey("THRESHOLD_BIRD_HEARD")
}
// Synchronous write operations
fun saveThreshold(value: Int) {
fun saveThresholdSpectrogram(value: Int) {
runBlocking {
context.dataStore.edit { preferences ->
preferences[THRESHOLD_KEY] = value;
preferences[THRESHOLD_SPECTROGRAM_KEY] = value;
}
}
}
fun getThreshold(): Int {
fun getThresholdSpectrogram(): Int {
return runBlocking {
context.dataStore.data.first()[THRESHOLD_KEY] ?: 50
context.dataStore.data.first()[THRESHOLD_SPECTROGRAM_KEY] ?: 50
}
}
// Synchronous write operations
fun saveThresholdWaveform(value: Int) {
runBlocking {
context.dataStore.edit { preferences ->
preferences[THRESHOLD_WAVEFORM_KEY] = value;
}
}
}
fun getThresholdWaveform(): Int {
return runBlocking {
context.dataStore.data.first()[THRESHOLD_WAVEFORM_KEY] ?: 50
}
}
// Synchronous write operations
fun saveBirdHeardThreshold(value: Int) {
runBlocking {
context.dataStore.edit { preferences ->
preferences[THRESHOLD_BIRDHEARD_KEY] = value;
}
}
}
fun getBirdHeardThreshold(): Int {
return runBlocking {
context.dataStore.data.first()[THRESHOLD_BIRDHEARD_KEY] ?: 50
}
}
}
class MainActivity : AppCompatActivity() {
// private lateinit var soundClassifier: SoundClassifier
val REQUEST_PERMISSIONS = 1337
private lateinit var dataStore: SynchronousDataStore
@@ -88,15 +123,16 @@ class MainActivity : AppCompatActivity() {
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")
soundClassifierSpectrogramBased = SoundClassifierSpectrogramBased(this);
Location.requestLocation(this, soundClassifier!!, soundClassifierSpectrogramBased!!);
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)
@@ -104,26 +140,79 @@ class MainActivity : AppCompatActivity() {
}
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)
val configuration = ResamplerConfiguration(
quality = ResamplerQuality.BEST,
inputChannel = ResamplerChannel.MONO,
inputSampleRate = 48000,
outputChannel = ResamplerChannel.MONO,
outputSampleRate = 16000
)
resampler = Resampler(configuration);
Settings.threshold = dataStore.getThreshold();
seekBar.progress = Settings.threshold
thresholdText.text = String.format("%.2f", Settings.threshold/100f)
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
val thresholdText_birdsinging = findViewById<TextView>(R.id.threshold_value_text_birdheard)
val seekBar_bird_singing = findViewById<SeekBar>(R.id.threshold_set_scale_bar_birdheard);
Settings.threshold_bird_singing = dataStore.getBirdHeardThreshold();
seekBar_bird_singing.progress = Settings.threshold_bird_singing
thresholdText_birdsinging.text =
String.format("%.2f", Settings.threshold_bird_singing / 100f)
seekBar_bird_singing.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)
dataStore.saveBirdHeardThreshold(actualValue);
Settings.threshold_bird_singing = actualValue;
thresholdText_birdsinging.text = String.format("%.2f", actualValue / 100f)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
val thresholdText_waveform = findViewById<TextView>(R.id.threshold_value_text_waveform)
val seekBar_Waveform = findViewById<SeekBar>(R.id.threshold_set_scale_bar_waveform);
Settings.threshold_waveform = dataStore.getThresholdWaveform();
seekBar_Waveform.progress = Settings.threshold_waveform
thresholdText_waveform.text = String.format("%.2f", Settings.threshold_waveform / 100f)
seekBar_Waveform.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
@SuppressLint("DefaultLocale")
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
var actualValue = progress
dataStore.saveThresholdWaveform(actualValue);
Settings.threshold_waveform = actualValue;
thresholdText_waveform.text = String.format("%.2f", actualValue / 100f)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
val thresholdText_spectrogram =
findViewById<TextView>(R.id.threshold_value_text_spectrogram)
val seekBar_Spectrogram = findViewById<SeekBar>(R.id.threshold_set_scale_bar_spectrogram);
Settings.threshold_spectrogram = dataStore.getThresholdSpectrogram();
seekBar_Spectrogram.progress = Settings.threshold_spectrogram
thresholdText_spectrogram.text =
String.format("%.2f", Settings.threshold_spectrogram / 100f)
seekBar_Spectrogram.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
@SuppressLint("DefaultLocale")
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
var actualValue = progress
dataStore.saveThresholdSpectrogram(actualValue);
Settings.threshold_spectrogram = actualValue;
thresholdText_spectrogram.text = String.format("%.2f", actualValue / 100f)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
@@ -132,11 +221,21 @@ class MainActivity : AppCompatActivity() {
})
}
fun set_image_view(bitmap: Bitmap) {
val imageView = this.findViewById<ImageView>(R.id.imageView)
imageView.setImageBitmap(bitmap)
}
companion object {
var soundClassifier: SoundClassifier? = null
// fun getSoundClassifier(): SoundClassifier? {
// return soundClassifier
// }
var soundClassifierSpectrogramBased: SoundClassifierSpectrogramBased? = null
var sound_buffer_48000hz = ShortArray(48000 * 10) // Store last 10 seconds in buffer
var sound_buffer_22050hz = ShortArray(22050 * 10) // Store last 10 seconds in buffer
var resampler: Resampler? = null
}
@@ -147,12 +246,12 @@ class MainActivity : AppCompatActivity() {
updateJob = lifecycleScope.launch {
while (isActive) { // isActive is a property of the coroutine scope
updateCounter++
if (last_message_time == 0.toLong())
{
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"
} else {
last_message_delay.text = "Last message: ${
(Instant.now().toEpochMilli() - last_message_time) / 1000F.toInt()
} seconds ago"
}
// last_message_delay.text = "Update count: $updateCounter"
@@ -171,8 +270,7 @@ class MainActivity : AppCompatActivity() {
val perms = mutableListOf<String>()
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
this, Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
perms.add(Manifest.permission.ACCESS_COARSE_LOCATION)
@@ -1,16 +1,25 @@
package com.birdsounds.identify
import android.graphics.Bitmap
import android.graphics.Color
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 io.github.nailik.androidresampler.Resampler
import io.github.nailik.androidresampler.ResamplerConfiguration
import io.github.nailik.androidresampler.data.ResamplerChannel
import io.github.nailik.androidresampler.data.ResamplerQuality
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.time.Instant
import java.util.LinkedList
import java.util.Queue
var last_message_time = 0L;
@@ -24,6 +33,75 @@ fun ByteArray.toLong(): Long {
return result
}
fun ShortArray.chunked(size: Int): List<ShortArray> {
return this.asList()
.chunked(size)
.map { it.toShortArray() }
}
fun FloatArray.chunked(size: Int): List<FloatArray> {
return this.asList()
.chunked(size)
.map { it.toFloatArray() }
}
fun List<ShortArray>.mergeToShortArray(): ShortArray {
// Calculate total size needed
val totalSize = this.sumOf { it.size }
// Create the output array
val result = ShortArray(totalSize)
// Copy each array into the result
var position = 0
for (array in this) {
array.copyInto(result, position)
position += array.size
}
return result
}
//var sound_buffer_48000hz = ShortArray(48000*10) // Store last 10 seconds in buffer
//var sound_buffer_22050hz = ShortArray(22050*10) // Store last 10 seconds in buffer
var sound_buffer_48000hz: Queue<ShortArray> = LinkedList<ShortArray>();
var sound_buffer_22050hz: Queue<ShortArray> = LinkedList<ShortArray>();
val configuration = ResamplerConfiguration(
quality = ResamplerQuality.BEST,
inputChannel = ResamplerChannel.MONO,
inputSampleRate = 48000,
outputChannel = ResamplerChannel.MONO,
outputSampleRate = 22050
)
val resampler = Resampler(configuration);
fun getPixelValue(i: Int, is_invert: Boolean): Int {
return if (is_invert) 255 - i else i
}
fun getPixelColor(i: Int, is_rgb: Boolean, is_invert: Boolean): Int {
val pixelValue = getPixelValue(i, is_invert);
if (is_rgb) {
return toRgb(pixelValue);
}
return toGrayscale(pixelValue);
}
fun toGrayscale(i: Int): Int {
return Color.rgb(i, i, i);
}
fun toRgb(i: Int): Int {
return Color.argb(255, i, i, i);
}
class MessageListenerService : WearableListenerService() {
@@ -32,9 +110,10 @@ class MessageListenerService : WearableListenerService() {
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")
Log.w(TAG, "Data recv: " + p0.data.size.toString() + " bytes")
val soundclassifier = MainActivity.soundClassifier
val soundClassifierSpectrogramBased = MainActivity.soundClassifierSpectrogramBased;
if (soundclassifier == null) {
Log.w(TAG, "Have invalid sound classifier")
return
@@ -42,6 +121,14 @@ class MessageListenerService : WearableListenerService() {
Log.w(TAG, "Have valid classifier")
}
if (soundClassifierSpectrogramBased == null) {
Log.w(TAG, "Have invalid spectrogram classifier")
return
} else {
Log.w(TAG, "Have valid spectrogram classifier")
}
var tstamp_bytes = p0.data.copyOfRange(0, Long.SIZE_BYTES)
last_message_time = tstamp_bytes.toLong()
@@ -53,8 +140,7 @@ class MessageListenerService : WearableListenerService() {
val buffer = ByteBuffer.wrap(audio_bytes_og)
val sound_a = ByteArrayOutputStream();
val byteArrayList = mutableListOf<ByteArray>()
while (buffer.hasRemaining())
{
while (buffer.hasRemaining()) {
val num_to_read = buffer.get().toInt()
val read_this = ByteArray(num_to_read)
buffer.get(read_this)
@@ -62,25 +148,94 @@ class MessageListenerService : WearableListenerService() {
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());
val audio_bytes = sound_a.toByteArray()
val short_array = ShortArray(audio_bytes.size / 2)
ByteBuffer.wrap(audio_bytes).order(
ByteOrder.LITTLE_ENDIAN
).asShortBuffer().get(short_array)
// Log.e(TAG, pcm_byte_array.sum().toString())
val audio_bytes_resampled = resampler.resample(audio_bytes)
if (true) {
val short_array_resampled = ShortArray(audio_bytes_resampled.size / 2)
ByteBuffer.wrap(audio_bytes_resampled).order(
ByteOrder.LITTLE_ENDIAN
).asShortBuffer().get(short_array_resampled)
val float_array_resampled = short_array_resampled.map { it.toFloat() }.toFloatArray()
val resampled_chunks: List<FloatArray> = float_array_resampled.chunked(512)
val freq_results = mutableListOf<ShortArray>()
for (seg in resampled_chunks) {
val add_this: ShortArray = soundClassifierSpectrogramBased.generateSpectrogram(seg)
freq_results.add(add_this);
// val color_int: List<Int> = add_this.map { Color.rgb(it.toInt(), it.toInt(), it.toInt()) }
// val color_int: List<Int> = add_this.map { Color.argb(125,125, 200, 5) }
// ret_val.add(color_int)
}
val total_points = freq_results.sumOf { it.size }
val freq_array = ShortArray(total_points)
var position = 0;
for (arr in freq_results) {
arr.copyInto(freq_array, position)
position += arr.size
}
var bitmap_pixels = IntArray(freq_array.size);
val createBitmap =
Bitmap.createBitmap(resampled_chunks.size, 128, Bitmap.Config.ARGB_8888);
// Log.d(TAG,"${freq_array.size.toString()} ${createBitmap.width.toString()} ${createBitmap.height.toString()}")
val is_rgb = true;
var is_invert = false;
var height = createBitmap.height
var i = 0
for (i2 in 0 until height) {
val width = createBitmap.getWidth();
var i3 = 0;
while (i3 < width) {
bitmap_pixels[i] =
getPixelColor(
freq_array[(createBitmap.height * i3) + i2].toInt(),
is_rgb,
is_invert
)
i3++;
i++;
}
}
createBitmap.setPixels(
bitmap_pixels,
0,
createBitmap.width,
0,
0,
createBitmap.width,
createBitmap.height
);
soundClassifierSpectrogramBased.run_interpreter(createBitmap)
}
//if (sound_buffer_48000hz.size >= 1) {
var string_send: String = ""
if (true) {
if (true) {
Log.e(TAG, "STARTING SCORING");
var string_send: String = ""
var sorted_list = soundclassifier.executeScoring(short_array)
val array_score = short_array;
var sorted_list = soundclassifier.executeScoring(array_score)
Log.w(TAG, "FINISHED SCORING");
Log.w(TAG, "")
val threshold = Settings.threshold/100f
val threshold = Settings.threshold_waveform / 100f
for (i in 0 until 10) {
val score = sorted_list[i].value
if (score < threshold) {
@@ -89,13 +244,30 @@ class MessageListenerService : WearableListenerService() {
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+=';'
string_send += species_name + " ~";
string_send += ','
string_send += score.toString()
string_send += ';'
}
}
if (true) {
string_send+=soundClassifierSpectrogramBased.score_string
}
MessageSenderFromPhone.sendMessage("/audio", tstamp_bytes + string_send.toByteArray(), this)
Log.d(TAG, string_send)
MessageSenderFromPhone.sendMessage(
"/audio",
tstamp_bytes + string_send.toByteArray(), b v
this
)
}
if (sound_buffer_22050hz.size >= 129) {
}
}
}
@@ -1,11 +1,43 @@
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"
fun create_file_map(pkg_path: String): Map<String, String> {
return mapOf(
"pkg" to pkg_path,
"file" to pkg_path.replace("-", "_").replace("/", "_")
)
}
var threshold: Int = 50;
object Settings {
var threshold_bird_singing: Int = 75
var threshold_spectrogram: Int = 10
var threshold_waveform: Int = 10;
var spectrogram_classify_model_file = create_file_map("from_merlin/sound_id_v40.tflite");
var spectrogram_classify_label_file = create_file_map("from_merlin/sound_id_v40.labels");
var spectrogram_geo_model_file = create_file_map("from_merlin/geo_v40.tflite")
var spectrogram_geo_label_file = create_file_map("from_merlin/geo_v40.labels")
var spectrogram_generate_model_file =
create_file_map("from_merlin/msid685v4_spectrogram.tflite");
var waveform_classify_model_file = create_file_map("2024_08_16/audio-model.tflite");
var waveform_meta_model_file = create_file_map("2024_08_16/meta-model.tflite");
var short_to_long_map = create_file_map("from_merlin/short_to_long.map");
val all_files = listOf(
spectrogram_classify_model_file,
spectrogram_classify_label_file,
spectrogram_geo_model_file,
spectrogram_geo_label_file,
spectrogram_generate_model_file,
waveform_classify_model_file,
waveform_meta_model_file,
short_to_long_map
)
}
@@ -7,6 +7,7 @@ import android.os.SystemClock
import android.preference.PreferenceManager
import android.util.Log
import org.tensorflow.lite.Interpreter
import java.io.BufferedReader
import java.io.File
import java.io.IOException
@@ -99,9 +100,7 @@ class SoundClassifier(
private lateinit var metaInputBuffer: FloatBuffer
private var model_ready = false;
init {;
setupDecoder(context)
loadLabels(context)
loadAssetList(context)
setupInterpreter(context)
setupMetaInterpreter(context)
warmUpModel()
@@ -112,49 +111,12 @@ class SoundClassifier(
{
return this.model_ready;
}
private fun setupDecoder(context: Context) {
}
/** Retrieve asset list from "asset_list" file */
private fun loadAssetList(context: Context) {
try {
val reader =
BufferedReader(InputStreamReader(context.assets.open(options.assetFile)))
val wordList = mutableListOf<String>()
reader.useLines { lines ->
lines.forEach {
wordList.add(it.trim())
}
}
assetList = wordList.map { it }
} catch (e: IOException) {
Log.e(TAG, "Failed to read labels ${options.assetFile}: ${e.message}")
}
}
/** Retrieve labels from "labels.txt" file */
private fun loadLabels(context: Context) {
val localeList = context.resources.configuration.locales
val language = localeList.get(0).language
var filename = options.labelsBase + "_${language}.txt"
var filename = "labels_en.txt"
//Check if file exists
val assetManager = context.assets // Replace 'assets' with actual AssetManager instance
try {
val mapList = assetManager.list("")?.toMutableList()
if (mapList != null) {
if (!mapList.contains(filename)) {
filename = options.labelsBase + "_en.txt"
}
}
} catch (ex: IOException) {
ex.printStackTrace()
filename = options.labelsBase + "_en.txt"
}
Log.i(TAG, filename)
try {
val reader =
BufferedReader(InputStreamReader(context.assets.open(filename)))
@@ -179,7 +141,7 @@ class SoundClassifier(
context.getDir(
"",
Context.MODE_PRIVATE
).absolutePath + "/" + settings.local_model_file;
).absolutePath + "/" + settings.waveform_classify_model_file["file"].toString();
Log.i(TAG, "Trying to create TFLite buffer from $modelFilePath")
val modelFile = File(modelFilePath)
@@ -190,6 +152,9 @@ class SoundClassifier(
Log.i(TAG, "Done creating TFLite buffer from $modelFilePath")
interpreter = Interpreter(tfliteBuffer, Interpreter.Options())
} catch (e: IOException) {
Log.e(TAG, "Failed to load TFLite model - ${e.message}")
return
@@ -222,7 +187,7 @@ class SoundClassifier(
val metaModelFilePath = context.getDir(
"",
Context.MODE_PRIVATE
).absolutePath + "/" + settings.local_meta_model_file
).absolutePath + "/" + settings.waveform_meta_model_file["file"].toString();
Log.i(TAG, "Trying to create TFLite buffer from $metaModelFilePath")
val metaModelFile = File(metaModelFilePath)
val tfliteBuffer: ByteBuffer =
@@ -270,6 +235,7 @@ class SoundClassifier(
metaInputBuffer.put(1, lon)
metaInputBuffer.put(2, weekMeta.toFloat())
metaInputBuffer.rewind() // Reset position to beginning of buffer
val metaOutputBuffer = FloatBuffer.allocate(metaModelNumClasses)
metaOutputBuffer.rewind()
meta_interpreter.run(metaInputBuffer, metaOutputBuffer)
@@ -0,0 +1,342 @@
package com.birdsounds.identify
import android.content.Context
import android.graphics.Bitmap
import android.location.Location
import android.media.Image
import android.util.Log
import android.widget.ImageView
import org.apache.commons.math3.stat.descriptive.summary.Sum
import org.tensorflow.lite.Interpreter
import org.tensorflow.lite.support.image.ImageProcessor
import org.tensorflow.lite.support.image.TensorImage
import org.tensorflow.lite.support.image.ops.ResizeOp
import org.tensorflow.lite.support.label.Category
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer
import org.tensorflow.lite.task.core.BaseOptions
import org.tensorflow.lite.task.vision.classifier.Classifications
import org.tensorflow.lite.task.vision.classifier.ImageClassifier
import org.tensorflow.lite.task.vision.classifier.ImageClassifier.ImageClassifierOptions
import java.io.File
import java.nio.ByteBuffer
import java.nio.FloatBuffer
import java.nio.channels.FileChannel
import java.nio.file.StandardOpenOption
import java.time.LocalDate
import java.time.ZoneId
import java.util.Calendar
import java.util.Calendar.WEEK_OF_YEAR
import java.util.Date
var local_species_spectrogram = "";
fun ByteBuffer.toUByteArray(): UByteArray {
// Get remaining bytes in the buffer
val bytes = ByteArray(this.remaining())
// Copy bytes from the buffer
this.get(bytes)
// Convert to UByteArray
return bytes.toUByteArray()
}
class SoundClassifierSpectrogramBased(context_in: MainActivity) {
private var c_threshold: Float = 0.0f;
private var model_ready: Boolean = false;
private var context: MainActivity = context_in;
var norm_factor: Float = (1 shl 15).toFloat();
var score_string = "";
var image_resize_op =
ImageProcessor.Builder().add(ResizeOp(128, 512, ResizeOp.ResizeMethod.BILINEAR)).build();
private lateinit var interpreter_spectrogram: Interpreter;
private var spec_inp_dim: IntArray = intArrayOf(); // [512]
private var spec_out_dim: IntArray = intArrayOf(); // [128,1]
private lateinit var specInputBuffer: TensorBuffer;
private lateinit var specOutputBuffer: TensorBuffer;
private lateinit var interpreter_geo: Interpreter;
private var geo_inp_dim: IntArray = intArrayOf();
private var geo_out_dim: IntArray = intArrayOf();
private lateinit var interpreter_classify: Interpreter;
private lateinit var interpreter_imc: ImageClassifier;
private var classify_inp_dim: IntArray = intArrayOf();
private var classify_out_dim: IntArray = intArrayOf();
private lateinit var classify_input_buffer: FloatBuffer;
private lateinit var classify_input_array: FloatArray;
private lateinit var classify_output_buffer: FloatBuffer;
private lateinit var classify_output_array: FloatArray;
private lateinit var labels_geo: Array<String>
private lateinit var labels_classify: Array<String>
private lateinit var geo_input: FloatBuffer
private lateinit var geo_output: FloatArray
private var geo_prob_map = mutableMapOf<String, Float>();
var short_long_map = mutableMapOf<String, String>()
init {;
loadMetaLabels()
loadLabels()
loadShortLongMap()
setupSpectrogramGenerator()
setupInterpreter()
setupMetaInterpreter()
this.model_ready = true;
}
private fun setupSpectrogramGenerator() {
interpreter_spectrogram =
load_interpreter(Settings.spectrogram_generate_model_file["file"].toString())
var spec_inp = this.interpreter_spectrogram.getInputTensor(/* inputIndex = */ 0)
specInputBuffer = TensorBuffer.createFixedSize(spec_inp.shape(), spec_inp.dataType())
var spec_out = this.interpreter_spectrogram.getOutputTensor(/* inputIndex = */ 0)
specOutputBuffer = TensorBuffer.createFixedSize(spec_out.shape(), spec_out.dataType())
}
fun generateSpectrogram(input_array: FloatArray): ShortArray {
specInputBuffer.buffer.clear()
specOutputBuffer.buffer.clear()
val array_to_load = input_array.copyOf(512).map { it / norm_factor }.toFloatArray()
specInputBuffer.loadArray(array_to_load)
// Log.d(TAG, "Loaded Array: ${array_to_load.min()} ${array_to_load.max()}")
interpreter_spectrogram.run(specInputBuffer.buffer, specOutputBuffer.buffer);
val output = specOutputBuffer.intArray.map { it.toShort() }.toShortArray()
return output
}
private fun setupMetaInterpreter() {
interpreter_geo = load_interpreter(Settings.spectrogram_geo_model_file["file"].toString())
geo_inp_dim = this.interpreter_geo.getInputTensor(0).shape()
Log.i(TAG, "TFLite model input shape: ${geo_inp_dim.contentToString()}")
geo_input = FloatBuffer.allocate(3);
geo_out_dim = this.interpreter_geo.getOutputTensor(0).shape()
Log.i(TAG, "TFLite model input shape: ${geo_out_dim.contentToString()}")
geo_output = FloatArray(geo_out_dim[0]) { 1f };
}
private fun setupInterpreter() {
interpreter_classify =
load_interpreter(Settings.spectrogram_classify_model_file["file"].toString())
interpreter_imc = ImageClassifier.createFromFileAndOptions(
File(get_file_path(Settings.spectrogram_classify_model_file["file"].toString())),
ImageClassifierOptions.builder().setBaseOptions(
BaseOptions.builder().setNumThreads(2).build()
).setMaxResults(10).build()
)
classify_inp_dim = this.interpreter_classify.getInputTensor(0).shape();
Log.i(TAG, "TFLite model input shape: ${classify_inp_dim.contentToString()}")
classify_out_dim = this.interpreter_classify.getOutputTensor(0).shape();
Log.i(TAG, "TFLite model output shape: ${classify_out_dim.contentToString()}")
val n_floats = classify_inp_dim.reduce(Int::times)
classify_input_buffer = FloatBuffer.allocate(n_floats)
classify_input_array = FloatArray(n_floats);
classify_output_buffer = FloatBuffer.allocate(classify_out_dim[1]);
classify_output_array = FloatArray(classify_out_dim[1]) { 0f };
}
private fun loadMetaLabels() {
labels_geo = load_labels(Settings.spectrogram_geo_label_file["file"].toString())
}
private fun loadLabels() {
labels_classify = load_labels(Settings.spectrogram_classify_label_file["file"].toString())
}
private fun loadShortLongMap() {
var labx = load_labels(Settings.short_to_long_map["file"].toString());
for (x in labx) {
var ff = x.split(",")
short_long_map[ff[0]] = ff[1]
}
}
private fun geo_prob_clamp(i: Float): Float {
return when {
i > 0.01 -> 1.0f
i > 0.008 -> 0.8f
i > 0.001 -> 0.5f
else -> 0.0f
}
}
fun runMetaInterpreter(location: Location) {
var localDate: LocalDate = LocalDate.now()
val date: Date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant())
val calendar = Calendar.getInstance()
calendar.time = date
val lon = this.asFloatArray(location.longitude.toFloat());
val lat = this.asFloatArray(location.latitude.toFloat());
val week_of_year = this.asFloatArray(calendar.get(WEEK_OF_YEAR).coerceAtMost(52).toFloat())
val input_array = arrayOf(lon, week_of_year, lat)
var outputTensor = interpreter_geo.getOutputTensor(0);
var fixedTensor =
TensorBuffer.createFixedSize(outputTensor.shape(), outputTensor.dataType())
val outputs: MutableMap<Int, Any> = mutableMapOf()
outputs[0] = fixedTensor.buffer;
interpreter_geo.runForMultipleInputsOutputs(input_array, outputs)
fixedTensor.floatArray.zip(labels_geo).forEach { (score, label) ->
geo_prob_map[label] = geo_prob_clamp(score)
}
val cloned = fixedTensor.floatArray.clone()
val sorted_indices = cloned.withIndex().sortedByDescending { it.value }.map { it.index }
local_species_spectrogram = "Most likely:\n"
for (i in 0..10) {
var c_species = labels_geo[sorted_indices[i]]
local_species_spectrogram += " " + short_long_map.get(c_species).toString();
local_species_spectrogram += "\n";
}
Log.d(TAG, "Likely: " + local_species_spectrogram)
}
fun is_model_ready(): Boolean {
return this.model_ready;
}
private fun get_file_path(file: String): String {
return this.context.getDir(
"", Context.MODE_PRIVATE
).absolutePath + "/" + file.toString();
}
private fun load_labels(file: String): Array<String> {
val label_file_path = this.get_file_path(file);
val labelFile = File(label_file_path)
return labelFile.readLines().toTypedArray()
}
private fun load_interpreter(file: String): Interpreter {
val modelFilePath = get_file_path(file);
val i = Log.i(TAG, "Trying to create TFLite buffer from $modelFilePath")
val modelFile = File(modelFilePath)
val tfliteBuffer: ByteBuffer =
FileChannel.open(modelFile.toPath(), StandardOpenOption.READ).use { channel ->
channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size())
}
Log.i(TAG, "Done creating TFLite buffer from $modelFilePath")
return Interpreter(tfliteBuffer, Interpreter.Options())
}
private fun asFloatArray(d: Float): FloatArray {
return floatArrayOf(d)
}
fun run_interpreter(createBitmap: Bitmap) {
val fromBitmap = TensorImage.fromBitmap(createBitmap);
val out: TensorImage = image_resize_op.process(fromBitmap);
this.context.runOnUiThread {
this.context.set_image_view(out.bitmap)
}
var classify: MutableList<Classifications> = interpreter_imc.classify(out)
var redone_scores = FloatArray(classify_output_array.size) { 0f };
var cats: MutableList<Category>? = classify.get(0).categories;
score_string = "";
cats?.forEach {
val c_name = it.label
val index = it.index
val score = it.score
var cur_score = 0f;
if (geo_prob_map.containsKey(c_name)) {
cur_score = score * geo_prob_map[c_name]!!
}
if (c_name == "bird1") {
cur_score = score
c_threshold = Settings.threshold_bird_singing / 100f;
}
else
{
c_threshold = Settings.threshold_spectrogram / 100f
}
redone_scores[index] = cur_score
if (cur_score > c_threshold) {
var use_name = "";
if (short_long_map.containsKey(c_name)) {
use_name = short_long_map[c_name].toString()
} else {
use_name = c_name
};
score_string += use_name + "_" + use_name;
score_string += ","
score_string += cur_score.toString();
score_string += ';'
}
}
//
//
// val outList = redone_scores.withIndex().sortedByDescending { it -> it.value };
// for (i in 0 until 6) {
// var cs: IndexedValue<Float> = outList[i]
// Log.d(TAG, i.toString() + " " + labels_classify[cs.index] + " " + cs.value.toString())
// }
//
// Log.d(TAG, classify_output_buffer.toString())
// Log.d(TAG, classify_output_array.sum().toString())
}
}
+143 -12
View File
@@ -12,14 +12,12 @@
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingTop="15dp"
android:text="Threshold:"
android:textSize="34sp" />
android:layout_height="100dp"
android:background="#ffffff"
android:padding="5dp" />
<LinearLayout
android:layout_width="match_parent"
@@ -27,23 +25,156 @@
android:orientation="horizontal">
<TextView
android:id="@+id/threshold_value_text"
android:layout_width="match_parent"
android:id="@+id/editTextText2"
android:layout_width="35dp"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToOutline="false"
android:clipToPadding="false"
android:ems="10"
android:gravity="center_horizontal|center_vertical"
android:inputType="text"
android:padding="0dp"
android:paddingHorizontal="0dp"
android:paddingVertical="0dp"
android:paddingStart="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:paddingEnd="0dp"
android:paddingRight="0dp"
android:paddingBottom="0dp"
android:rotation="270"
android:singleLine="false"
android:text="Merlin"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="10sp" />
<TextView
android:id="@+id/threshold_value_text_spectrogram"
android:layout_width="76dp"
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:id="@+id/threshold_set_scale_bar_spectrogram"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center|fill_horizontal|fill_vertical"
android:max="100"
android:min="0"
android:progress="20" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="55dp"
android:orientation="horizontal">
<TextView
android:id="@+id/editTextText5"
android:layout_width="35dp"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToOutline="false"
android:clipToPadding="true"
android:drawablePadding="0dp"
android:ems="10"
android:gravity="center_horizontal|center_vertical"
android:includeFontPadding="false"
android:inputType="text"
android:padding="0dp"
android:paddingHorizontal="0dp"
android:paddingVertical="0dp"
android:paddingStart="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:paddingEnd="0dp"
android:paddingRight="0dp"
android:paddingBottom="0dp"
android:rotation="270"
android:singleLine="false"
android:text="Regular"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="10sp" />
<TextView
android:id="@+id/threshold_value_text_waveform"
android:layout_width="76dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="Threshold:"
android:textAlignment="center"
android:textSize="28sp" />
<SeekBar
android:id="@+id/threshold_set_scale_bar_waveform"
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" />
android:min="0"
android:progress="20" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="55dp"
android:orientation="horizontal">
<TextView
android:id="@+id/editTextText4"
android:layout_width="35dp"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToOutline="false"
android:clipToPadding="false"
android:ems="10"
android:gravity="center_horizontal|center_vertical"
android:inputType="textMultiLine"
android:lines="2"
android:maxLines="2"
android:minLines="2"
android:padding="0dp"
android:paddingHorizontal="0dp"
android:paddingVertical="0dp"
android:paddingStart="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:paddingEnd="0dp"
android:paddingRight="0dp"
android:paddingBottom="0dp"
android:rotation="270"
android:singleLine="false"
android:text="Bird\nHeard"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="10sp" />
<TextView
android:id="@+id/threshold_value_text_birdheard"
android:layout_width="76dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="Threshold:"
android:textAlignment="center"
android:textSize="28sp" />
<SeekBar
android:id="@+id/threshold_set_scale_bar_birdheard"
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"
android:progress="80" />
</LinearLayout>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 0 B

-6
View File
@@ -1,6 +0,0 @@
<resources>
<string name="app_name">identify</string>
<string name="title_activity_download">DownloadActivity</string>
<string name="error_download">Download Error</string>
<string name="samples_zero">Samples are all zero</string>
</resources>
@@ -61,7 +61,8 @@ fun FlashingText(
modifier = modifier
.background(color = backgroundColor),
textDecor = textDecor,
fontStyle = fontStyle
fontStyle = fontStyle,
key = key
)
}
@@ -72,6 +73,7 @@ fun AutoSizedText(
textAlign: TextAlign = TextAlign.Center,
modifier: Modifier = Modifier,
textDecor: TextDecoration = TextDecoration.None,
key: Long = 0,
minFontSize: TextUnit = 5.sp,
targetFontSize: TextUnit = 24.sp,
fontWeight: FontWeight = FontWeight.Normal,
@@ -115,7 +117,9 @@ fun AutoSizedText(
// Convert the best font size back to sp and use it
// val bestFontSize = with(density) { bestFontSizePx.toSp() }
LaunchedEffect(key1 = key) {
}
Text(
text = display_text,
@@ -12,7 +12,6 @@ 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
@@ -41,7 +40,6 @@ 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
@@ -53,7 +51,6 @@ 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
@@ -166,7 +163,7 @@ fun WearApp() {
) {
composable("species_list") {
ScreenScaffold {
SpeciesListView(context = context, appState = mainState.appState)
SpeciesListView(context = context, appState = mainState.appState, birdHeardState = mainState.birdHeardState, mainState = mainState )
}
}
@@ -20,8 +20,14 @@ import java.time.Duration
class MainState(private val activity: Activity, private val requestPermission: () -> Unit) {
private val playbackStateMutatorMutex = MutatorMutex()
var appState by mutableStateOf<AppState>(AppState.Ready)
private set
var birdHeardState by mutableStateOf<BirdHeardState>(BirdHeardState.NotHearing)
var recordingProgress by mutableStateOf(0f)
private set
var isPermissionDenied by mutableStateOf(false)
@@ -82,6 +88,13 @@ sealed class AppState {
object Recording : AppState()
}
sealed class BirdHeardState {
object Hearing : BirdHeardState()
object NotHearing : BirdHeardState()
}
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
private suspend fun record(activity: (Activity),soundRecorder: SoundRecorder,
setProgress: (progress: Float) -> Unit,
@@ -1,6 +1,7 @@
package com.birdsounds.identify.presentation
import android.content.Context
import android.util.Log
import androidx.compose.ui.platform.LocalContext
import com.google.android.gms.wearable.MessageEvent
import com.google.android.gms.wearable.WearableListenerService
@@ -16,6 +17,7 @@ class MessageListenerService : WearableListenerService() {
{
this_context = context;
}
override fun onMessageReceived(p0: MessageEvent) {
super.onMessageReceived(p0)
val t_scored = ByteBuffer.wrap(p0.data).getLong()
@@ -27,12 +29,14 @@ class MessageListenerService : WearableListenerService() {
var do_vibrate = false;
var did_add = false;
var new_entries = 0;
SpeciesList.clear_new_flags();
// bird_heard_state = BirdHeardState.NotHearing
SpeciesList.bird_not_heard();
list_strings.map({
var split_str = it.split(',')
if (split_str.size == 2) {
if ((split_str.size == 2) && !(split_str[0]=="bird1_bird1")) {
Log.d(TAG, split_str[0] + "${split_str[0] == "bird1"}")
new_entries+=1;
var out = AScore(split_str[0], split_str[1].toFloat(), t_scored);
var out = AScore(split_str[0], split_str[1].toFloat(), t_scored, true);
out.new_entry = true;
if (SpeciesList.add_observation(out))
@@ -41,8 +45,16 @@ class MessageListenerService : WearableListenerService() {
}
do_vibrate = true;
}
else if (split_str[0]=="bird1_bird1") {
Log.d(TAG, "I AM HEARING A BIRD")
SpeciesList.bird_heard();
// bird_heard_state = BirdHeardState.Hearing
}
})
// SpeciesList.set_num_new_entries(new_entries);
SpeciesList.num_new_entries = new_entries;
// SpeciesList.insert_new_entry_spacer(new_entries);
if (did_add) {
vibrateDouble(this, 100, 250, 100);
@@ -31,6 +31,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import java.nio.ByteOrder
fun vibrateDouble(
context: Context,
@@ -40,7 +41,8 @@ fun vibrateDouble(
) {
// 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
val vibratorManager =
context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
@Suppress("DEPRECATION")
@@ -64,7 +66,8 @@ fun vibrateDouble(
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
val vibratorManager =
context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
@Suppress("DEPRECATION")
@@ -113,7 +116,8 @@ class SoundRecorder(
val frameSize = Constants.FrameSize._120();
val chunkSize = frameSize.v * 2; // Mono * 2 bytes per sample
val counts_until_reset = 3 * sampleRateInHz / frameSize.v;
val seconds_per_message = 3
val counts_until_reset = seconds_per_message * sampleRateInHz / frameSize.v;
val bufferSize =
AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
@@ -126,15 +130,14 @@ class SoundRecorder(
)
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;
var min_val = 1000000f
var max_val = -1000000f
while (true) /**/ {
if (Thread.interrupted()) {
audioRecord.release();
@@ -155,10 +158,16 @@ class SoundRecorder(
var offset = 0
var remained = frame.size
while (remained > 0) {
val read = audioRecord.read(frame, offset, remained)
val read: Int = audioRecord.read(frame, offset, remained)
offset += read
remained -= read
}
val s_arry = ShortArray(frame.size / 2)
ByteBuffer.wrap(frame).order(
ByteOrder.LITTLE_ENDIAN
).asShortBuffer().get(s_arry);
min_val = Math.min(s_arry.min().toFloat(), min_val)
max_val = Math.max(s_arry.max().toFloat(), max_val);
val encoded: ByteArray? =
@@ -174,7 +183,7 @@ class SoundRecorder(
codec_opus.encoderRelease();
count = 0;
val writtenBytes = outputByteStream.position()
Log.e(TAG,"Wrote ${writtenBytes} bytes!")
Log.e(TAG, "Wrote ${writtenBytes} bytes!")
val duplicate = outputByteStream.duplicate()
duplicate.flip() // Prepare for reading
// outputByteStream.flip()
@@ -185,8 +194,14 @@ class SoundRecorder(
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()}")
Log.e(
TAG,
"Sending message of 3s with size ${byte_send.size} + ${byte_send[0].toString()}"
)
MessageSender.sendMessage("/audio", byte_send, context)
Log.d(TAG, "${min_val} ${max_val} ")
min_val = 1000000f
max_val = -1000000f
}
@@ -12,27 +12,22 @@ import java.time.Instant
class AScore(
_species: String,
_score: Float,
_timestamp: Long,
_trigger: Boolean = false,
_new_entry: Boolean = false,
_redraw_number: Long = 0,
val _species: String,
val _score: Float,
val _timestamp: Long,
val _new_entry: Boolean = false,
val _trigger: Boolean = false,
val _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)
@@ -48,10 +43,10 @@ class AScore(
var internal_list = mutableListOf<AScore>()
object SpeciesList {
var trigger_redraw = 0;
lateinit var _mainState: MainState
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;
}
@@ -63,7 +58,7 @@ object SpeciesList {
}
fun add_observation(species_in: AScore): Boolean {
Log.w(TAG, "In add observation")
var idx = 0
var idx_replace = -1
var added_new = false;
@@ -74,7 +69,6 @@ object SpeciesList {
idx += 1
}
if (idx_replace >= 0) {
Log.w(TAG, "Replacing")
internal_list[idx_replace] = species_in
internal_list[idx_replace].trigger = true;
} else {
@@ -94,6 +88,7 @@ object SpeciesList {
fun update_list_on_ui_thread(
) {
// _list_on_ui!!.clear();
while (_list_on_ui!!.size < internal_list.size) {
_list_on_ui!!.add(mutableStateOf(AScore("", 0.0F, 0L)))
}
@@ -101,9 +96,24 @@ object SpeciesList {
for ((index, value) in internal_list.withIndex()) {
_list_on_ui?.get(index)?.value = value;
}
}
fun bird_not_heard() {
_mainState.birdHeardState= BirdHeardState.NotHearing;
Log.d(TAG, "I set the state to not hearing")
}
fun bird_heard() {
_mainState.birdHeardState = BirdHeardState.Hearing;
Log.d(TAG, "I set the state to hearing")
}
fun setBirdHeardVar(birdHeardState: BirdHeardState) {
}
fun setMainState(mainState: MainState) {
_mainState = mainState;
Log.d(TAG, "I set the internal app main state variable")
}
}
@@ -2,6 +2,9 @@ package com.birdsounds.identify.presentation
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.text.format.DateFormat
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
@@ -11,17 +14,18 @@ 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.Face
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.Mic
import androidx.compose.material.icons.rounded.MicOff
import androidx.compose.material.icons.rounded.MusicNote
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
@@ -29,11 +33,13 @@ 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.TextUnit
import androidx.compose.ui.unit.TextUnitType
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.LocalContentColor
import androidx.wear.compose.material.TimeTextDefaults
import androidx.wear.compose.material.TimeText
import androidx.wear.compose.material.curvedText
@@ -67,11 +73,13 @@ fun interpolateColor(value: Float): Color {
val species_list_show = mutableStateListOf<MutableState<AScore>>()
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SpeciesListView(
context: Context, appState: AppState,
context: Context, appState: AppState, birdHeardState: BirdHeardState, mainState: MainState,
) {
@@ -80,7 +88,7 @@ fun SpeciesListView(
species_list_show.add(hi);
}
SpeciesList.setSpeciecsShow_list(species_list_show)
SpeciesList.setMainState(mainState);
val species_show: SnapshotStateList<MutableState<AScore>> = remember { species_list_show }
// var sP = scalingParams( maxTransitionArea= 0.25f, minTransitionArea = 0.05f)
@@ -96,6 +104,16 @@ fun SpeciesListView(
),
)
var battery_level = remember { mutableStateOf(getBatteryLevel(context)) }
LaunchedEffect(key1 = Unit) {
while (true) {
battery_level.value = getBatteryLevel(context)
delay(60000) // Update every minute
}
}
val text_counter = remember { mutableStateOf("Initial text") }
LaunchedEffect(key1 = true) {
while (true) {
@@ -115,7 +133,6 @@ fun SpeciesListView(
ScreenScaffold(
scrollState = columnState
) {
@@ -125,23 +142,34 @@ fun SpeciesListView(
DateFormat.getBestDateTimePattern(Locale.getDefault(), "hh:mm")
),
startCurvedContent = {
curvedComposable {
Icon(
Icons.Rounded.MusicNote,
contentDescription = "",
modifier = Modifier.size(14.dp),
tint = if (mainState.birdHeardState == BirdHeardState.Hearing
) Color.Green else Color.Black
)
}
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
modifier = Modifier.size(14.dp)
)
}
// curvedText(
// text = if (appState == AppState.Recording) "" else "Inactive0",
//// textAlign = TextAlign.Center,
// )
},
endCurvedContent = {
curvedText(text = text_counter.value)//, textAlign = TextAlign.Center)
curvedText(
text = text_counter.value + " · " + battery_level.value+"%",
fontSize = TextUnit(value = 14F, type = TextUnitType.Sp)
)
//, textAlign = TextAlign.Center)`
}
@@ -156,45 +184,29 @@ fun SpeciesListView(
) {
ScalingLazyColumn(
columnState = columnState,
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
columnState = columnState
) {
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;"
// if (isNewEntry) {
// text_style = FontStyle.Italic;
// aSpec.value.new_entry = false;
// }
Log.w(
TAG,
"Adding Separator: " + index.toString() + " " + SpeciesList.num_new_entries + " " + add_sep.toString()
index.toString() + " " + currentName + " " + isNewEntry.toString() + " " + shouldTrigger.toString()
)
aSpec.value.new_entry = false;
// var composables: List<@Composable () -> Unit> = listOf();
key(currentName, isNewEntry, shouldTrigger) {
if (shouldTrigger) {
Log.w(TAG, "Trigger " + aSpec.toString())
Row {
FlashingText(
text = aSpec.value.common_name,
fontStyle = text_style,
@@ -207,51 +219,42 @@ fun SpeciesListView(
delay(100);
aSpec.value.trigger = false
}
Log.e("TAG", "BLINKING")
} else {
}
} else {
Row {
AutoSizedText(
text = aSpec.value.common_name,
color = interpolateColor(aSpec.value.score),
modifier = Modifier.fillMaxSize(),
textAlign = TextAlign.Center,
key = Instant.now().toEpochMilli(),
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
//
// }
// }
// Get current battery level
private fun getBatteryLevel(context: Context): Int {
val batteryStatus: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { iFilter ->
context.registerReceiver(null, iFilter)
}
val level = batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
val scale = batteryStatus?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
return if (level != -1 && scale != -1) {
level * 100 / scale
} else {
-1
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 0 B