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> <selectionStates>
<SelectionState runConfigName="mobile"> <SelectionState runConfigName="mobile">
<option name="selectionMode" value="DROPDOWN" /> <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"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=RFAX803LMFR" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=569cc5b0" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>
@@ -15,10 +15,10 @@
</SelectionState> </SelectionState>
<SelectionState runConfigName="wear"> <SelectionState runConfigName="wear">
<option name="selectionMode" value="DROPDOWN" /> <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"> <Target type="DEFAULT_BOOT">
<handle> <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> </handle>
</Target> </Target>
</DropdownSelection> </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" datastoreCoreAndroid = "1.1.3"
coreKtxVersion = "1.13.0" coreKtxVersion = "1.13.0"
animationCoreAndroid = "1.6.6" animationCoreAndroid = "1.6.6"
#litertSupportApi = "1.2.0"
#litert = "1.2.0"
#litert = "1.0.1" #litert = "1.0.1"
[libraries] [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" } stately-concurrent-collections = { module = "co.touchlab:stately-concurrent-collections", version.ref = "statelyConcurrentCollections" }
ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "uiTooling" } 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" } 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" } #litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "litert" }
[plugins] [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"))
}
+10 -2
View File
@@ -51,13 +51,19 @@ dependencies {
implementation(libs.androidx.activity) implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.work.runtime.ktx) 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.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom)) implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui) implementation(libs.androidx.ui)
implementation("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("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("com.google.android.gms:play-services-tflite:20.0.0")
implementation("uk.me.berndporr:iirj:1.7") implementation("uk.me.berndporr:iirj:1.7")
implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.graphics)
@@ -65,6 +71,8 @@ dependencies {
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
implementation(libs.androidx.datastore.core.android) implementation(libs.androidx.datastore.core.android)
implementation(libs.core.ktx) implementation(libs.core.ktx)
// implementation(libs.litert.support.api)
// implementation(libs.litert)
// implementation(libs.litert) // implementation(libs.litert)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
Binary file not shown.
@@ -42,11 +42,13 @@ class Downloader(mainActivity: MainActivity) {
} }
} }
fun prepareModelFiles() fun prepareModelFiles() {
{
copyAssetToFolder(settings.pkg_model_file, settings.local_model_file); for (files_to_copy in Settings.all_files) {
copyAssetToFolder(settings.pkg_meta_model_file, settings.local_meta_model_file); copyAssetToFolder(
files_to_copy["pkg"].toString(),
files_to_copy["file"].toString()
)
}
} }
} }
@@ -31,7 +31,11 @@ object Location {
locationListenerGPS = null locationListenerGPS = null
} }
fun requestLocation(context: Context, soundClassifier: SoundClassifier) { fun requestLocation(
context: Context,
soundClassifier: SoundClassifier,
soundClassifierSpectrogramBased: SoundClassifierSpectrogramBased
) {
if (ActivityCompat.checkSelfPermission( if (ActivityCompat.checkSelfPermission(
context, context,
Manifest.permission.ACCESS_COARSE_LOCATION Manifest.permission.ACCESS_COARSE_LOCATION
@@ -44,18 +48,28 @@ object Location {
if (locationListenerGPS == null) locationListenerGPS = object : LocationListener { if (locationListenerGPS == null) locationListenerGPS = object : LocationListener {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onLocationChanged(location: Location) { override fun onLocationChanged(location: Location) {
Log.w(TAG, "Got location changed");
while (!soundClassifier.is_model_ready()) {
Thread.sleep(50); Log.w(TAG, "Got location changed");
} while (!soundClassifier.is_model_ready()) {
Log.w(TAG, "Sound classifier is ready"); Thread.sleep(50);
soundClassifier.runMetaInterpreter(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; val activity = context as? Activity;
activity?.let { activity?.let {
val text_species: TextView = it.findViewById(R.id.local_species) 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) val loc_lon: TextView = it.findViewById(R.id.location_long)
loc_lon.text = loc_lon.text =
@@ -71,12 +85,7 @@ object Location {
geocoder.getFromLocation(location.latitude, location.longitude, 1) geocoder.getFromLocation(location.latitude, location.longitude, 1)
if (addresses?.isNotEmpty() == true) { if (addresses?.isNotEmpty() == true) {
val address: Address = addresses[0] val address: Address = addresses[0]
// loc_string.text = address.locality.toString() + ", " + address.adminArea.toString() + " " + address.countryName.toString()
loc_string.text = address.getAddressLine(0).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.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.util.Log import android.util.Log
import android.widget.ImageView
import android.widget.SeekBar import android.widget.SeekBar
import android.widget.TextView import android.widget.TextView
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
@@ -22,11 +23,13 @@ import androidx.datastore.preferences.preferencesDataStore
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.gms.wearable.ChannelClient import com.google.android.gms.wearable.ChannelClient
import com.google.android.gms.wearable.Wearable 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.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -36,8 +39,6 @@ private var updateJob: Job? = null
private var updateCounter = 0 private var updateCounter = 0
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings") val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
val Any.TAG: String val Any.TAG: String
get() { get() {
val tag = javaClass.simpleName val tag = javaClass.simpleName
@@ -49,30 +50,64 @@ class SynchronousDataStore(private val context: Context) {
// Define keys // Define keys
companion object { 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 saveThresholdSpectrogram(value: Int) {
fun saveThreshold(value: Int) {
runBlocking { runBlocking {
context.dataStore.edit { preferences -> context.dataStore.edit { preferences ->
preferences[THRESHOLD_KEY] = value; preferences[THRESHOLD_SPECTROGRAM_KEY] = value;
} }
} }
} }
fun getThreshold(): Int { fun getThresholdSpectrogram(): Int {
return runBlocking { 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() { class MainActivity : AppCompatActivity() {
// private lateinit var soundClassifier: SoundClassifier
val REQUEST_PERMISSIONS = 1337 val REQUEST_PERMISSIONS = 1337
private lateinit var dataStore: SynchronousDataStore private lateinit var dataStore: SynchronousDataStore
@@ -88,15 +123,16 @@ class MainActivity : AppCompatActivity() {
super.onChannelOpened(channel) super.onChannelOpened(channel)
Log.d(TAG, "onChannelOpened") Log.d(TAG, "onChannelOpened")
} }
} })
)
Downloader(this).prepareModelFiles(); Downloader(this).prepareModelFiles();
Log.w(TAG, "Finished setting up downloader")
requestPermissions() requestPermissions()
soundClassifier = SoundClassifier(this, SoundClassifier.Options()) soundClassifier = SoundClassifier(this, SoundClassifier.Options())
Log.w(TAG, "Starting sound classifier") soundClassifierSpectrogramBased = SoundClassifierSpectrogramBased(this);
Location.requestLocation(this, soundClassifier!!)
Log.w(TAG, "Starting location requester") Location.requestLocation(this, soundClassifier!!, soundClassifierSpectrogramBased!!);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) 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) last_message_delay = findViewById<TextView>(R.id.last_message_delay)
dataStore = SynchronousDataStore(applicationContext) dataStore = SynchronousDataStore(applicationContext)
val configuration = ResamplerConfiguration(
quality = ResamplerQuality.BEST,
inputChannel = ResamplerChannel.MONO,
inputSampleRate = 48000,
outputChannel = ResamplerChannel.MONO,
outputSampleRate = 16000
)
resampler = Resampler(configuration);
val thresholdText_birdsinging = findViewById<TextView>(R.id.threshold_value_text_birdheard)
Settings.threshold = dataStore.getThreshold(); val seekBar_bird_singing = findViewById<SeekBar>(R.id.threshold_set_scale_bar_birdheard);
seekBar.progress = Settings.threshold Settings.threshold_bird_singing = dataStore.getBirdHeardThreshold();
thresholdText.text = String.format("%.2f", Settings.threshold/100f) seekBar_bird_singing.progress = Settings.threshold_bird_singing
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { thresholdText_birdsinging.text =
String.format("%.2f", Settings.threshold_bird_singing / 100f)
seekBar_bird_singing.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
var actualValue = progress var actualValue = progress
dataStore.saveThreshold(actualValue); dataStore.saveBirdHeardThreshold(actualValue);
Settings.threshold = actualValue; Settings.threshold_bird_singing = actualValue;
thresholdText.text = String.format("%.2f", actualValue/100f) 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) {} 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 { companion object {
var soundClassifier: SoundClassifier? = null var soundClassifier: SoundClassifier? = null
// fun getSoundClassifier(): SoundClassifier? { var soundClassifierSpectrogramBased: SoundClassifierSpectrogramBased? = null
// return soundClassifier
// } 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 { updateJob = lifecycleScope.launch {
while (isActive) { // isActive is a property of the coroutine scope while (isActive) { // isActive is a property of the coroutine scope
updateCounter++ updateCounter++
if (last_message_time == 0.toLong()) if (last_message_time == 0.toLong()) {
{
last_message_delay.text = "No messages received" last_message_delay.text = "No messages received"
} else } else {
{ last_message_delay.text = "Last message: ${
last_message_delay.text = "Last message: ${(Instant.now().toEpochMilli() - last_message_time)/1000F.toInt()} seconds ago" (Instant.now().toEpochMilli() - last_message_time) / 1000F.toInt()
} seconds ago"
} }
// last_message_delay.text = "Update count: $updateCounter" // last_message_delay.text = "Update count: $updateCounter"
@@ -171,8 +270,7 @@ class MainActivity : AppCompatActivity() {
val perms = mutableListOf<String>() val perms = mutableListOf<String>()
if (ContextCompat.checkSelfPermission( if (ContextCompat.checkSelfPermission(
this, this, Manifest.permission.ACCESS_COARSE_LOCATION
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED ) != PackageManager.PERMISSION_GRANTED
) { ) {
perms.add(Manifest.permission.ACCESS_COARSE_LOCATION) perms.add(Manifest.permission.ACCESS_COARSE_LOCATION)
@@ -1,16 +1,25 @@
package com.birdsounds.identify package com.birdsounds.identify
import android.graphics.Bitmap
import android.graphics.Color
import android.util.Log import android.util.Log
import com.google.android.gms.wearable.MessageEvent import com.google.android.gms.wearable.MessageEvent
import com.google.android.gms.wearable.WearableListenerService import com.google.android.gms.wearable.WearableListenerService
import com.theeasiestway.opus.Constants import com.theeasiestway.opus.Constants
import com.theeasiestway.opus.Opus import com.theeasiestway.opus.Opus
import decodeAACToPCM 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.io.ByteArrayOutputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.time.Instant import java.time.Instant
import java.util.LinkedList
import java.util.Queue
var last_message_time = 0L; var last_message_time = 0L;
@@ -24,6 +33,75 @@ fun ByteArray.toLong(): Long {
return result 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() { class MessageListenerService : WearableListenerService() {
@@ -32,9 +110,10 @@ class MessageListenerService : WearableListenerService() {
super.onMessageReceived(p0) super.onMessageReceived(p0)
val codec_opus = Opus() val codec_opus = Opus()
codec_opus.decoderInit(Constants.SampleRate._48000(), Constants.Channels.mono()) 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 soundclassifier = MainActivity.soundClassifier
val soundClassifierSpectrogramBased = MainActivity.soundClassifierSpectrogramBased;
if (soundclassifier == null) { if (soundclassifier == null) {
Log.w(TAG, "Have invalid sound classifier") Log.w(TAG, "Have invalid sound classifier")
return return
@@ -42,6 +121,14 @@ class MessageListenerService : WearableListenerService() {
Log.w(TAG, "Have valid classifier") 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) var tstamp_bytes = p0.data.copyOfRange(0, Long.SIZE_BYTES)
last_message_time = tstamp_bytes.toLong() last_message_time = tstamp_bytes.toLong()
@@ -53,8 +140,7 @@ class MessageListenerService : WearableListenerService() {
val buffer = ByteBuffer.wrap(audio_bytes_og) val buffer = ByteBuffer.wrap(audio_bytes_og)
val sound_a = ByteArrayOutputStream(); val sound_a = ByteArrayOutputStream();
val byteArrayList = mutableListOf<ByteArray>() val byteArrayList = mutableListOf<ByteArray>()
while (buffer.hasRemaining()) while (buffer.hasRemaining()) {
{
val num_to_read = buffer.get().toInt() val num_to_read = buffer.get().toInt()
val read_this = ByteArray(num_to_read) val read_this = ByteArray(num_to_read)
buffer.get(read_this) buffer.get(read_this)
@@ -62,40 +148,126 @@ class MessageListenerService : WearableListenerService() {
sound_a.write(decoded); sound_a.write(decoded);
// Log.e(TAG,"Decompressed ${read_this.size} to ${decoded?.size}") // Log.e(TAG,"Decompressed ${read_this.size} to ${decoded?.size}")
} }
val audio_bytes = sound_a.toByteArray()
codec_opus.decoderRelease(); 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( ByteBuffer.wrap(audio_bytes).order(
ByteOrder.LITTLE_ENDIAN ByteOrder.LITTLE_ENDIAN
).asShortBuffer().get(short_array) ).asShortBuffer().get(short_array)
// Log.e(TAG, pcm_byte_array.sum().toString())
Log.e(TAG, "STARTING SCORING");
var string_send: String = "" val audio_bytes_resampled = resampler.resample(audio_bytes)
var sorted_list = soundclassifier.executeScoring(short_array)
Log.w(TAG, "FINISHED SCORING"); if (true) {
Log.w(TAG, "") val short_array_resampled = ShortArray(audio_bytes_resampled.size / 2)
val threshold = Settings.threshold/100f ByteBuffer.wrap(audio_bytes_resampled).order(
for (i in 0 until 10) { ByteOrder.LITTLE_ENDIAN
val score = sorted_list[i].value ).asShortBuffer().get(short_array_resampled)
if (score < threshold) {
continue 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
} }
val index = sorted_list[i].index
val species_name = soundclassifier.labelList[index]
Log.w(TAG, species_name + ", " + score.toString())
string_send+= species_name
string_send+=','
string_send+=score.toString()
string_send+=';'
}
MessageSenderFromPhone.sendMessage("/audio", tstamp_bytes + string_send.toByteArray(), this)
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");
val array_score = short_array;
var sorted_list = soundclassifier.executeScoring(array_score)
Log.w(TAG, "FINISHED SCORING");
Log.w(TAG, "")
val threshold = Settings.threshold_waveform / 100f
for (i in 0 until 10) {
val score = sorted_list[i].value
if (score < threshold) {
continue
}
val index = sorted_list[i].index
val species_name = soundclassifier.labelList[index]
Log.w(TAG, species_name + ", " + score.toString())
string_send += species_name + " ~";
string_send += ','
string_send += score.toString()
string_send += ';'
}
}
if (true) {
string_send+=soundClassifierSpectrogramBased.score_string
}
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 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" fun create_file_map(pkg_path: String): Map<String, String> {
var pkg_meta_model_file: String = "2024_08_16/meta-model.tflite" 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.preference.PreferenceManager
import android.util.Log import android.util.Log
import org.tensorflow.lite.Interpreter import org.tensorflow.lite.Interpreter
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@@ -99,9 +100,7 @@ class SoundClassifier(
private lateinit var metaInputBuffer: FloatBuffer private lateinit var metaInputBuffer: FloatBuffer
private var model_ready = false; private var model_ready = false;
init {; init {;
setupDecoder(context)
loadLabels(context) loadLabels(context)
loadAssetList(context)
setupInterpreter(context) setupInterpreter(context)
setupMetaInterpreter(context) setupMetaInterpreter(context)
warmUpModel() warmUpModel()
@@ -112,49 +111,12 @@ class SoundClassifier(
{ {
return this.model_ready; 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 */ /** Retrieve labels from "labels.txt" file */
private fun loadLabels(context: Context) { private fun loadLabels(context: Context) {
val localeList = context.resources.configuration.locales var filename = "labels_en.txt"
val language = localeList.get(0).language
var filename = options.labelsBase + "_${language}.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 { try {
val reader = val reader =
BufferedReader(InputStreamReader(context.assets.open(filename))) BufferedReader(InputStreamReader(context.assets.open(filename)))
@@ -179,7 +141,7 @@ class SoundClassifier(
context.getDir( context.getDir(
"", "",
Context.MODE_PRIVATE 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") Log.i(TAG, "Trying to create TFLite buffer from $modelFilePath")
val modelFile = File(modelFilePath) val modelFile = File(modelFilePath)
@@ -190,6 +152,9 @@ class SoundClassifier(
Log.i(TAG, "Done creating TFLite buffer from $modelFilePath") Log.i(TAG, "Done creating TFLite buffer from $modelFilePath")
interpreter = Interpreter(tfliteBuffer, Interpreter.Options()) interpreter = Interpreter(tfliteBuffer, Interpreter.Options())
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Failed to load TFLite model - ${e.message}") Log.e(TAG, "Failed to load TFLite model - ${e.message}")
return return
@@ -222,7 +187,7 @@ class SoundClassifier(
val metaModelFilePath = context.getDir( val metaModelFilePath = context.getDir(
"", "",
Context.MODE_PRIVATE 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") Log.i(TAG, "Trying to create TFLite buffer from $metaModelFilePath")
val metaModelFile = File(metaModelFilePath) val metaModelFile = File(metaModelFilePath)
val tfliteBuffer: ByteBuffer = val tfliteBuffer: ByteBuffer =
@@ -270,6 +235,7 @@ class SoundClassifier(
metaInputBuffer.put(1, lon) metaInputBuffer.put(1, lon)
metaInputBuffer.put(2, weekMeta.toFloat()) metaInputBuffer.put(2, weekMeta.toFloat())
metaInputBuffer.rewind() // Reset position to beginning of buffer metaInputBuffer.rewind() // Reset position to beginning of buffer
val metaOutputBuffer = FloatBuffer.allocate(metaModelNumClasses) val metaOutputBuffer = FloatBuffer.allocate(metaModelNumClasses)
metaOutputBuffer.rewind() metaOutputBuffer.rewind()
meta_interpreter.run(metaInputBuffer, metaOutputBuffer) 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:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<TextView <ImageView
android:id="@+id/textView" android:id="@+id/imageView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="100dp"
android:paddingLeft="5dp" android:background="#ffffff"
android:paddingTop="15dp" android:padding="5dp" />
android:text="Threshold:"
android:textSize="34sp" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -27,23 +25,156 @@
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
android:id="@+id/threshold_value_text" android:id="@+id/editTextText2"
android:layout_width="match_parent" 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_height="match_parent"
android:layout_weight="4"
android:gravity="center" android:gravity="center"
android:text="Threshold:" android:text="Threshold:"
android:textAlignment="center" android:textAlignment="center"
android:textSize="28sp" /> android:textSize="28sp" />
<SeekBar <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_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center|fill_horizontal|fill_vertical" android:layout_gravity="center|fill_horizontal|fill_vertical"
android:layout_weight="1" android:layout_weight="1"
android:max="100" 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> </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 modifier = modifier
.background(color = backgroundColor), .background(color = backgroundColor),
textDecor = textDecor, textDecor = textDecor,
fontStyle = fontStyle fontStyle = fontStyle,
key = key
) )
} }
@@ -72,6 +73,7 @@ fun AutoSizedText(
textAlign: TextAlign = TextAlign.Center, textAlign: TextAlign = TextAlign.Center,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
textDecor: TextDecoration = TextDecoration.None, textDecor: TextDecoration = TextDecoration.None,
key: Long = 0,
minFontSize: TextUnit = 5.sp, minFontSize: TextUnit = 5.sp,
targetFontSize: TextUnit = 24.sp, targetFontSize: TextUnit = 24.sp,
fontWeight: FontWeight = FontWeight.Normal, fontWeight: FontWeight = FontWeight.Normal,
@@ -115,7 +117,9 @@ fun AutoSizedText(
// Convert the best font size back to sp and use it // Convert the best font size back to sp and use it
// val bestFontSize = with(density) { bestFontSizePx.toSp() } // val bestFontSize = with(density) { bestFontSizePx.toSp() }
LaunchedEffect(key1 = key) {
}
Text( Text(
text = display_text, text = display_text,
@@ -12,7 +12,6 @@ import android.app.Activity
import android.app.Notification import android.app.Notification
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.Service
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat.startForeground
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController 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.composable
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import androidx.wear.ongoing.OngoingActivity import androidx.wear.ongoing.OngoingActivity
import com.birdsounds.identify.R
import com.birdsounds.identify.presentation.theme.IdentifyTheme import com.birdsounds.identify.presentation.theme.IdentifyTheme
import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.audio.ui.VolumeViewModel import com.google.android.horologist.audio.ui.VolumeViewModel
@@ -166,7 +163,7 @@ fun WearApp() {
) { ) {
composable("species_list") { composable("species_list") {
ScreenScaffold { 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) { class MainState(private val activity: Activity, private val requestPermission: () -> Unit) {
private val playbackStateMutatorMutex = MutatorMutex() private val playbackStateMutatorMutex = MutatorMutex()
var appState by mutableStateOf<AppState>(AppState.Ready) var appState by mutableStateOf<AppState>(AppState.Ready)
private set
var birdHeardState by mutableStateOf<BirdHeardState>(BirdHeardState.NotHearing)
var recordingProgress by mutableStateOf(0f) var recordingProgress by mutableStateOf(0f)
private set private set
var isPermissionDenied by mutableStateOf(false) var isPermissionDenied by mutableStateOf(false)
@@ -82,6 +88,13 @@ sealed class AppState {
object Recording : AppState() object Recording : AppState()
} }
sealed class BirdHeardState {
object Hearing : BirdHeardState()
object NotHearing : BirdHeardState()
}
@RequiresPermission(Manifest.permission.RECORD_AUDIO) @RequiresPermission(Manifest.permission.RECORD_AUDIO)
private suspend fun record(activity: (Activity),soundRecorder: SoundRecorder, private suspend fun record(activity: (Activity),soundRecorder: SoundRecorder,
setProgress: (progress: Float) -> Unit, setProgress: (progress: Float) -> Unit,
@@ -1,6 +1,7 @@
package com.birdsounds.identify.presentation package com.birdsounds.identify.presentation
import android.content.Context import android.content.Context
import android.util.Log
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import com.google.android.gms.wearable.MessageEvent import com.google.android.gms.wearable.MessageEvent
import com.google.android.gms.wearable.WearableListenerService import com.google.android.gms.wearable.WearableListenerService
@@ -16,6 +17,7 @@ class MessageListenerService : WearableListenerService() {
{ {
this_context = context; this_context = context;
} }
override fun onMessageReceived(p0: MessageEvent) { override fun onMessageReceived(p0: MessageEvent) {
super.onMessageReceived(p0) super.onMessageReceived(p0)
val t_scored = ByteBuffer.wrap(p0.data).getLong() val t_scored = ByteBuffer.wrap(p0.data).getLong()
@@ -27,12 +29,14 @@ class MessageListenerService : WearableListenerService() {
var do_vibrate = false; var do_vibrate = false;
var did_add = false; var did_add = false;
var new_entries = 0; var new_entries = 0;
SpeciesList.clear_new_flags(); // bird_heard_state = BirdHeardState.NotHearing
SpeciesList.bird_not_heard();
list_strings.map({ list_strings.map({
var split_str = it.split(',') 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; 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; out.new_entry = true;
if (SpeciesList.add_observation(out)) if (SpeciesList.add_observation(out))
@@ -41,8 +45,16 @@ class MessageListenerService : WearableListenerService() {
} }
do_vibrate = true; 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); // SpeciesList.insert_new_entry_spacer(new_entries);
if (did_add) { if (did_add) {
vibrateDouble(this, 100, 250, 100); vibrateDouble(this, 100, 250, 100);
@@ -31,6 +31,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import java.nio.ByteOrder
fun vibrateDouble( fun vibrateDouble(
context: Context, context: Context,
@@ -40,7 +41,8 @@ fun vibrateDouble(
) { ) {
// Get the vibrator service // Get the vibrator service
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 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 vibratorManager.defaultVibrator
} else { } else {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@@ -64,7 +66,8 @@ fun vibrateDouble(
fun vibrate(context: Context, duration: Long = 500) { fun vibrate(context: Context, duration: Long = 500) {
val vibrator = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { 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 vibratorManager.defaultVibrator
} else { } else {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@@ -113,7 +116,8 @@ class SoundRecorder(
val frameSize = Constants.FrameSize._120(); val frameSize = Constants.FrameSize._120();
val chunkSize = frameSize.v * 2; // Mono * 2 bytes per sample 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 = val bufferSize =
AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
@@ -126,15 +130,14 @@ class SoundRecorder(
) )
val thread = Thread { val thread = Thread {
// var sent_first: Boolean = false // var sent_first: Boolean = false
val outputByteStream = ByteBuffer.allocate(1000000) val outputByteStream = ByteBuffer.allocate(1000000)
audioRecord.startRecording() audioRecord.startRecording()
var last_tstamp: Long = Instant.now().toEpochMilli(); var last_tstamp: Long = Instant.now().toEpochMilli();
var count: Int = 0; var count: Int = 0;
var min_val = 1000000f
var max_val = -1000000f
while (true) /**/ { while (true) /**/ {
if (Thread.interrupted()) { if (Thread.interrupted()) {
audioRecord.release(); audioRecord.release();
@@ -155,10 +158,16 @@ class SoundRecorder(
var offset = 0 var offset = 0
var remained = frame.size var remained = frame.size
while (remained > 0) { while (remained > 0) {
val read = audioRecord.read(frame, offset, remained) val read: Int = audioRecord.read(frame, offset, remained)
offset += read offset += read
remained -= 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? = val encoded: ByteArray? =
@@ -174,7 +183,7 @@ class SoundRecorder(
codec_opus.encoderRelease(); codec_opus.encoderRelease();
count = 0; count = 0;
val writtenBytes = outputByteStream.position() val writtenBytes = outputByteStream.position()
Log.e(TAG,"Wrote ${writtenBytes} bytes!") Log.e(TAG, "Wrote ${writtenBytes} bytes!")
val duplicate = outputByteStream.duplicate() val duplicate = outputByteStream.duplicate()
duplicate.flip() // Prepare for reading duplicate.flip() // Prepare for reading
// outputByteStream.flip() // outputByteStream.flip()
@@ -185,8 +194,14 @@ class SoundRecorder(
val tstamp_buffer = ByteBuffer.allocate(Long.SIZE_BYTES) val tstamp_buffer = ByteBuffer.allocate(Long.SIZE_BYTES)
val tstamp_bytes = tstamp_buffer.putLong(tstamp).array() val tstamp_bytes = tstamp_buffer.putLong(tstamp).array()
var byte_send: ByteArray = tstamp_bytes + bytes; 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) 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( class AScore(
_species: String, val _species: String,
_score: Float, val _score: Float,
_timestamp: Long, val _timestamp: Long,
_trigger: Boolean = false, val _new_entry: Boolean = false,
_new_entry: Boolean = false, val _trigger: Boolean = false,
_redraw_number: Long = 0, val _redraw_number: Long = 0,
) { ) {
var redraw_number = _redraw_number;
val split_stuff: List<String> = _species.split("_"); val split_stuff: List<String> = _species.split("_");
val species = split_stuff[0]; val species = split_stuff[0];
val score = _score; val score = _score;
var trigger = _trigger; var trigger = _trigger;
var new_entry = _new_entry; var new_entry = _new_entry;
// var common_name = split_stuff[1];
val common_name = if (split_stuff.size > 1) split_stuff[1] else ""; val common_name = if (split_stuff.size > 1) split_stuff[1] else "";
val timestamp = _timestamp val timestamp = _timestamp
fun set_redraw_number(redraw_num: Long)
{
redraw_number = redraw_num
}
fun age(): Long { fun age(): Long {
var tstamp: Long = Instant.now().toEpochMilli() var tstamp: Long = Instant.now().toEpochMilli()
return (tstamp - timestamp) return (tstamp - timestamp)
@@ -48,10 +43,10 @@ class AScore(
var internal_list = mutableListOf<AScore>() var internal_list = mutableListOf<AScore>()
object SpeciesList { object SpeciesList {
var trigger_redraw = 0; lateinit var _mainState: MainState
var num_new_entries = 0; var num_new_entries = 0;
var do_add_observation = false
var _list_on_ui: SnapshotStateList<MutableState<AScore>>? = null var _list_on_ui: SnapshotStateList<MutableState<AScore>>? = null
fun setSpeciecsShow_list(list_in: SnapshotStateList<MutableState<AScore>>) { fun setSpeciecsShow_list(list_in: SnapshotStateList<MutableState<AScore>>) {
_list_on_ui = list_in; _list_on_ui = list_in;
} }
@@ -63,7 +58,7 @@ object SpeciesList {
} }
fun add_observation(species_in: AScore): Boolean { fun add_observation(species_in: AScore): Boolean {
Log.w(TAG, "In add observation")
var idx = 0 var idx = 0
var idx_replace = -1 var idx_replace = -1
var added_new = false; var added_new = false;
@@ -74,7 +69,6 @@ object SpeciesList {
idx += 1 idx += 1
} }
if (idx_replace >= 0) { if (idx_replace >= 0) {
Log.w(TAG, "Replacing")
internal_list[idx_replace] = species_in internal_list[idx_replace] = species_in
internal_list[idx_replace].trigger = true; internal_list[idx_replace].trigger = true;
} else { } else {
@@ -94,6 +88,7 @@ object SpeciesList {
fun update_list_on_ui_thread( fun update_list_on_ui_thread(
) { ) {
// _list_on_ui!!.clear();
while (_list_on_ui!!.size < internal_list.size) { while (_list_on_ui!!.size < internal_list.size) {
_list_on_ui!!.add(mutableStateOf(AScore("", 0.0F, 0L))) _list_on_ui!!.add(mutableStateOf(AScore("", 0.0F, 0L)))
} }
@@ -101,9 +96,24 @@ object SpeciesList {
for ((index, value) in internal_list.withIndex()) { for ((index, value) in internal_list.withIndex()) {
_list_on_ui?.get(index)?.value = value; _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.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.text.format.DateFormat import android.text.format.DateFormat
import android.util.Log import android.util.Log
import androidx.compose.foundation.layout.Arrangement 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.fillMaxWidth
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons 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.Mic
import androidx.compose.material.icons.rounded.MicOff import androidx.compose.material.icons.rounded.MicOff
import androidx.compose.material.icons.rounded.MusicNote
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.font.FontStyle
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration 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.compose.ui.unit.dp
import androidx.wear.compose.foundation.curvedComposable import androidx.wear.compose.foundation.curvedComposable
import androidx.wear.compose.foundation.lazy.items
import androidx.wear.compose.foundation.lazy.itemsIndexed import androidx.wear.compose.foundation.lazy.itemsIndexed
import androidx.wear.compose.material.Icon import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.LocalContentColor
import androidx.wear.compose.material.TimeTextDefaults import androidx.wear.compose.material.TimeTextDefaults
import androidx.wear.compose.material.TimeText import androidx.wear.compose.material.TimeText
import androidx.wear.compose.material.curvedText import androidx.wear.compose.material.curvedText
@@ -67,11 +73,13 @@ fun interpolateColor(value: Float): Color {
val species_list_show = mutableStateListOf<MutableState<AScore>>() val species_list_show = mutableStateListOf<MutableState<AScore>>()
@SuppressLint("UnrememberedMutableState") @SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalHorologistApi::class) @OptIn(ExperimentalHorologistApi::class)
@Composable @Composable
fun SpeciesListView( 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); species_list_show.add(hi);
} }
SpeciesList.setSpeciecsShow_list(species_list_show) SpeciesList.setSpeciecsShow_list(species_list_show)
SpeciesList.setMainState(mainState);
val species_show: SnapshotStateList<MutableState<AScore>> = remember { species_list_show } val species_show: SnapshotStateList<MutableState<AScore>> = remember { species_list_show }
// var sP = scalingParams( maxTransitionArea= 0.25f, minTransitionArea = 0.05f) // 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") } val text_counter = remember { mutableStateOf("Initial text") }
LaunchedEffect(key1 = true) { LaunchedEffect(key1 = true) {
while (true) { while (true) {
@@ -115,7 +133,6 @@ fun SpeciesListView(
ScreenScaffold( ScreenScaffold(
scrollState = columnState scrollState = columnState
) { ) {
@@ -125,23 +142,34 @@ fun SpeciesListView(
DateFormat.getBestDateTimePattern(Locale.getDefault(), "hh:mm") DateFormat.getBestDateTimePattern(Locale.getDefault(), "hh:mm")
), ),
startCurvedContent = { startCurvedContent = {
curvedComposable {
Icon(
Icons.Rounded.MusicNote,
contentDescription = "",
modifier = Modifier.size(14.dp),
tint = if (mainState.birdHeardState == BirdHeardState.Hearing
) Color.Green else Color.Black
)
}
curvedComposable { curvedComposable {
Icon( Icon(
if (appState == AppState.Recording) Icons.Rounded.Mic else Icons.Rounded.MicOff, if (appState == AppState.Recording) Icons.Rounded.Mic else Icons.Rounded.MicOff,
contentDescription = "", contentDescription = "",
modifier = Modifier.size(16.dp) modifier = Modifier.size(14.dp)
// tint = androidx.compose.ui.graphics.Color.Red
) )
} }
// curvedText(
// text = if (appState == AppState.Recording) "" else "Inactive0",
//// textAlign = TextAlign.Center,
// )
}, },
endCurvedContent = { 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,102 +184,77 @@ fun SpeciesListView(
) { ) {
ScalingLazyColumn( ScalingLazyColumn(
columnState = columnState, modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth() columnState = columnState
) { ) {
itemsIndexed( itemsIndexed(
species_show, species_show,
) { index, aSpec -> ) { index, aSpec ->
var text_decor = TextDecoration.None var text_decor = TextDecoration.None
var text_style = FontStyle.Normal; var text_style = FontStyle.Normal;
val currentName = aSpec.value.common_name val currentName = aSpec.value.common_name
val currentScore = aSpec.value.score
val isNewEntry = aSpec.value.new_entry val isNewEntry = aSpec.value.new_entry
val shouldTrigger = aSpec.value.trigger val shouldTrigger = aSpec.value.trigger
// if (isNewEntry) {
var add_sep = false; // text_style = FontStyle.Italic;
if (aSpec.value.new_entry) { // aSpec.value.new_entry = false;
// add_sep = true;
text_style = FontStyle.Italic;
// aSpec.value.new_entry = false
}
Log.w(TAG, "Species " + currentName + " " + aSpec.value.new_entry.toString())
LaunchedEffect(Unit) {
delay(100);
aSpec.value.new_entry = false
}
// if (index == (SpeciesList.num_new_entries-1)) {
// add_sep = true;"
// } // }
Log.w( Log.w(
TAG, 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) { key(currentName, isNewEntry, shouldTrigger) {
if (shouldTrigger) { if (shouldTrigger) {
Log.w(TAG, "Trigger " + aSpec.toString()) Log.w(TAG, "Trigger " + aSpec.toString())
FlashingText( Row {
text = aSpec.value.common_name, FlashingText(
fontStyle = text_style, text = aSpec.value.common_name,
color = interpolateColor(aSpec.value.score), fontStyle = text_style,
key = Instant.now().toEpochMilli(), color = interpolateColor(aSpec.value.score),
textDecor = text_decor, key = Instant.now().toEpochMilli(),
modifier = Modifier.fillMaxSize() textDecor = text_decor,
) modifier = Modifier.fillMaxSize()
LaunchedEffect(Unit) { )
delay(100); LaunchedEffect(Unit) {
aSpec.value.trigger = false delay(100);
aSpec.value.trigger = false
}
} }
Log.e("TAG", "BLINKING")
} else {
} else {
Row {
AutoSizedText( AutoSizedText(
text = aSpec.value.common_name, text = aSpec.value.common_name,
color = interpolateColor(aSpec.value.score), color = interpolateColor(aSpec.value.score),
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
key = Instant.now().toEpochMilli(),
textDecor = text_decor, textDecor = text_decor,
fontStyle = text_style, fontStyle = text_style,
) )
}
Log.e("TAG", "Not blinking") 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()) { // Get current battery level
// items(species_show) { aSpec -> Text(text = aSpec.value.common_name) private fun getBatteryLevel(context: Context): Int {
// } // Dynamically display the chips 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