Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f2522d2
feat(voice): implement "Hey Rio" hotword wake-up for Google Assistant
kangrio Aug 14, 2026
69ca2ef
feat: Add voice model training and hotword detection settings
kangrio Aug 15, 2026
7968b43
refactor: Use toUri extension function for URI parsing
kangrio Aug 15, 2026
17646d2
ui: Add descriptions for settings and adjust row layout
kangrio Aug 15, 2026
a1fc33e
ui: Refine voice training workflow and status messages
kangrio Aug 15, 2026
b6c19fd
ui: Enable vertical scrolling in SettingsActivity
kangrio Aug 15, 2026
28ac327
refactor: Reuse Toast instance and add showToast helper in VoiceWakeS…
kangrio Aug 15, 2026
4584150
feat: Support custom hotword model persistence and reset functionality
kangrio Aug 15, 2026
2d142ad
refactor: Improve StartActivity startup flow and permission handling
kangrio Aug 16, 2026
ff1cb7e
feat: Support multiple wake word models and custom selection
kangrio Aug 16, 2026
bf21e08
fix: Stop existing detector before re-initialization in VoiceWakeService
kangrio Aug 16, 2026
4de73e6
feat: Replace specific package queries with QUERY_ALL_PACKAGES permis…
kangrio Aug 16, 2026
24da290
feat: Implement support selecting any voice assistant app
kangrio Aug 16, 2026
fd4ec53
refactor: Simplify assistant package component assignment logic
kangrio Aug 16, 2026
dbda432
feat: Add permission onboarding wizard
kangrio Aug 16, 2026
fdb8a9c
fix: Call finishAffinity in onPause in SettingsActivity
kangrio Aug 16, 2026
afa7be7
ui: Update onboarding permission descriptions and AI examples
kangrio Aug 16, 2026
e03c5a9
Merge branch 'main' into dev-origin
kangrio Aug 16, 2026
c2746a8
ui: Increase voice assistant notification importance to HIGH
kangrio Aug 16, 2026
2f7915e
refactor: Simplify assistant intent configuration in Utils
kangrio Aug 16, 2026
2d8fd38
build: Add beta build type
kangrio Aug 16, 2026
a54c253
chore: bump version 1.0.2
kangrio Aug 16, 2026
8b96bb0
refactor: Remove legacy MainActivity and its manifest registration
kangrio Aug 16, 2026
e342fd5
ui: Refine onboarding UI colors
kangrio Aug 16, 2026
8ed3dfe
Merge branch 'main' into dev
kangrio Aug 17, 2026
112990c
feat: Start SettingsActivity upon completing permission onboarding
kangrio Aug 17, 2026
5fb0740
fix: can't start voice assistant from background
kangrio Aug 17, 2026
57db0b0
feat: Add assistant notification permission management and automate v…
kangrio Aug 17, 2026
7bf8c9b
ui: Enable vertical scrolling in assistant app selection dialog
kangrio Aug 17, 2026
cf20e08
refactor: Extract voice assistant launch logic to Utils
kangrio Aug 17, 2026
d87edd3
fix: Prevent multiple clicks during recording countdown
kangrio Aug 17, 2026
05184c5
Merge branch 'main' into dev
kangrio Aug 17, 2026
478a0dd
docs: Update README to reflect wake-word detection and multi-assistan…
kangrio Aug 17, 2026
894bf21
feat: Implement OTA update system using GitHub Releases
kangrio Aug 17, 2026
1a3fd3f
refactor: Add SerializedName annotations to GitHub API data classes
kangrio Aug 17, 2026
2544dde
bump: Release version 1.0.2
kangrio Aug 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- v1.0.2
• In-App Updates: Added OTA updates via GitHub Releases.
• Offline Wake Word: Added Snowboy hotword detection (hey_rio, alexa, snowboy) with gain and sensitivity controls.
• Custom Model Training: Added in-app voice sample recording and hotword training via Seasalt API.
• Multi-Assistant Support: Added selection support for any installed assistant app (Google, ChatGPT, Claude, Perplexity).
• Onboarding Wizard: Added setup screen for Mic, Overlay, Auto-Start, local ADB, and Notification permissions.
• Background Launch Fix: Fixed voice assistant launch issues from background service.
• UI & UX Polish: Added vertical scrolling to dialogs, debounce on record button, and activation chime sound.
• CI / Build: Added beta build workflow and bumped version to 1.0.2.
217 changes: 123 additions & 94 deletions README.md

Large diffs are not rendered by default.

27 changes: 23 additions & 4 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ plugins {
alias(libs.plugins.kotlin.compose)
}

val runNumber = providers.environmentVariable("GITHUB_RUN_NUMBER").orElse("0")

android {
namespace = "com.kangrio.byd.assistant"
compileSdk {
Expand All @@ -18,8 +20,11 @@ android {
applicationId = "com.kangrio.byd.assistant"
minSdk = 24
targetSdk = 37
versionCode = 2
versionName = "1.0.1"
versionCode = 3
versionName = "1.0.2"
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64")
}

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand All @@ -44,10 +49,17 @@ android {
optimization {
enable = true
}
if (file("signing.properties").exists()) {
signingConfig = signingConfigs.getByName("release")
signingConfig = if (file("signing.properties").exists()) {
signingConfigs.getByName("release")
} else {
signingConfigs.getByName("debug")
}
}

register("beta") {
initWith(getByName("release"))
versionNameSuffix = "-beta.${runNumber.get()}"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
Expand All @@ -56,6 +68,9 @@ android {
buildFeatures {
compose = true
}
sourceSets["main"].apply {
jniLibs.srcDirs("src/main/jniLibs")
}
}

androidComponents {
Expand All @@ -72,6 +87,10 @@ androidComponents {
dependencies {
implementation(libs.dadb)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.okhttp)
implementation(libs.retrofit)
implementation(libs.retrofit.converter.gson)
implementation(libs.accompanist.drawablepainter)

implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose)
Expand Down
Binary file not shown.
Binary file not shown.
49 changes: 38 additions & 11 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />

<queries>
<package android:name="com.google.android.googlequicksearchbox" />
<intent>
<action android:name="android.intent.action.VOICE_COMMAND" />
</intent>
</queries>
<!--HotWord-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

<application
android:name=".App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -27,26 +31,49 @@
android:supportsRtl="true"
android:theme="@style/Theme.Assistant"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="false"
android:theme="@style/Theme.Assistant" />
<activity
android:name=".StartActivity"
android:exported="true"
android:excludeFromRecents="true"
android:theme="@style/Theme.Start">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".activity.SettingsActivity"
android:excludeFromRecents="true"
android:exported="false"/>

<activity
android:name=".activity.PermissionOnboardingActivity"
android:excludeFromRecents="true"
android:exported="false"
android:theme="@style/Theme.Assistant" />

<receiver
android:name=".receiver.BootReceiver"
android:exported="true">
<intent-filter>
<intent-filter android:priority="100">
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>

<service
android:name=".service.VoiceWakeService"
android:exported="false"
android:foregroundServiceType="microphone" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
Binary file added app/src/main/assets/snowboy/common.res
Binary file not shown.
Binary file added app/src/main/assets/snowboy/models/alexa.pmdl
Binary file not shown.
Binary file added app/src/main/assets/snowboy/models/hey_rio.pmdl
Binary file not shown.
Binary file added app/src/main/assets/snowboy/models/snowboy.pmdl
Binary file not shown.
104 changes: 104 additions & 0 deletions app/src/main/java/ai/kitt/snowboy/SnowboyDetect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/* ----------------------------------------------------------------------------
* This file was automatically generated by SWIG (http://www.swig.org).
* Version 4.0.2
*
* Do not make changes to this file unless you know what you are doing--modify
* the SWIG interface file instead.
* ----------------------------------------------------------------------------- */

package ai.kitt.snowboy;

public class SnowboyDetect {
private transient long swigCPtr;
protected transient boolean swigCMemOwn;

protected SnowboyDetect(long cPtr, boolean cMemoryOwn) {
swigCMemOwn = cMemoryOwn;
swigCPtr = cPtr;
}

protected static long getCPtr(SnowboyDetect obj) {
return (obj == null) ? 0 : obj.swigCPtr;
}

@SuppressWarnings("deprecation")
protected void finalize() {
delete();
}

public synchronized void delete() {
if (swigCPtr != 0) {
if (swigCMemOwn) {
swigCMemOwn = false;
snowboyJNI.delete_SnowboyDetect(swigCPtr);
}
swigCPtr = 0;
}
}

public SnowboyDetect(String resource_filename, String model_str) {
this(snowboyJNI.new_SnowboyDetect(resource_filename, model_str), true);
}

public boolean Reset() {
return snowboyJNI.SnowboyDetect_Reset(swigCPtr, this);
}

public int RunDetection(String data, int is_end) {
return snowboyJNI.SnowboyDetect_RunDetection__SWIG_0(swigCPtr, this, data, is_end);
}

public int RunDetection(String data) {
return snowboyJNI.SnowboyDetect_RunDetection__SWIG_1(swigCPtr, this, data);
}

public int RunDetection(float[] data, int array_length, boolean is_end) {
return snowboyJNI.SnowboyDetect_RunDetection__SWIG_2(swigCPtr, this, data, array_length, is_end);
}

public int RunDetection(float[] data, int array_length) {
return snowboyJNI.SnowboyDetect_RunDetection__SWIG_3(swigCPtr, this, data, array_length);
}

public int RunDetection(short[] data, int array_length, boolean is_end) {
return snowboyJNI.SnowboyDetect_RunDetection__SWIG_4(swigCPtr, this, data, array_length, is_end);
}

public int RunDetection(short[] data, int array_length) {
return snowboyJNI.SnowboyDetect_RunDetection__SWIG_5(swigCPtr, this, data, array_length);
}

public int RunDetection(int[] data, int array_length, boolean is_end) {
return snowboyJNI.SnowboyDetect_RunDetection__SWIG_6(swigCPtr, this, data, array_length, is_end);
}

public int RunDetection(int[] data, int array_length) {
return snowboyJNI.SnowboyDetect_RunDetection__SWIG_7(swigCPtr, this, data, array_length);
}

public void SetSensitivity(String sensitivity_str) {
snowboyJNI.SnowboyDetect_SetSensitivity(swigCPtr, this, sensitivity_str);
}



public String GetSensitivity() {
return snowboyJNI.SnowboyDetect_GetSensitivity(swigCPtr, this);
}

public void SetAudioGain(float audio_gain) {
snowboyJNI.SnowboyDetect_SetAudioGain(swigCPtr, this, audio_gain);
}

public void UpdateModel() {
snowboyJNI.SnowboyDetect_UpdateModel(swigCPtr, this);
}

public int NumHotwords() {
return snowboyJNI.SnowboyDetect_NumHotwords(swigCPtr, this);
}

public void ApplyFrontend(boolean apply_frontend) {
snowboyJNI.SnowboyDetect_ApplyFrontend(swigCPtr, this, apply_frontend);
}
}
101 changes: 101 additions & 0 deletions app/src/main/java/ai/kitt/snowboy/SnowboyVad.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* ----------------------------------------------------------------------------
* This file was automatically generated by SWIG (http://www.swig.org).
* Version 4.0.1
*
* Do not make changes to this file unless you know what you are doing--modify
* the SWIG interface file instead.
* ----------------------------------------------------------------------------- */

package ai.kitt.snowboy;

public class SnowboyVad {
private transient long swigCPtr;
protected transient boolean swigCMemOwn;

protected SnowboyVad(long cPtr, boolean cMemoryOwn) {
swigCMemOwn = cMemoryOwn;
swigCPtr = cPtr;
}



protected static long getCPtr(SnowboyVad obj) {
return (obj == null) ? 0 : obj.swigCPtr;
}

@SuppressWarnings("deprecation")
protected void finalize() {
delete();
}

public synchronized void delete() {
if (swigCPtr != 0) {
if (swigCMemOwn) {
swigCMemOwn = false;
snowboyJNI.delete_SnowboyVad(swigCPtr);
}
swigCPtr = 0;
}
}

public SnowboyVad(String resource_filename) {
this(snowboyJNI.new_SnowboyVad(resource_filename), true);
}

public boolean Reset() {
return snowboyJNI.SnowboyVad_Reset(swigCPtr, this);
}

public int RunVad(String data, boolean is_end) {
return snowboyJNI.SnowboyVad_RunVad__SWIG_0(swigCPtr, this, data, is_end);
}

public int RunVad(String data) {
return snowboyJNI.SnowboyVad_RunVad__SWIG_1(swigCPtr, this, data);
}

public int RunVad(float[] data, int array_length, boolean is_end) {
return snowboyJNI.SnowboyVad_RunVad__SWIG_2(swigCPtr, this, data, array_length, is_end);
}

public int RunVad(float[] data, int array_length) {
return snowboyJNI.SnowboyVad_RunVad__SWIG_3(swigCPtr, this, data, array_length);
}

public int RunVad(short[] data, int array_length, boolean is_end) {
return snowboyJNI.SnowboyVad_RunVad__SWIG_4(swigCPtr, this, data, array_length, is_end);
}

public int RunVad(short[] data, int array_length) {
return snowboyJNI.SnowboyVad_RunVad__SWIG_5(swigCPtr, this, data, array_length);
}

public int RunVad(int[] data, int array_length, boolean is_end) {
return snowboyJNI.SnowboyVad_RunVad__SWIG_6(swigCPtr, this, data, array_length, is_end);
}

public int RunVad(int[] data, int array_length) {
return snowboyJNI.SnowboyVad_RunVad__SWIG_7(swigCPtr, this, data, array_length);
}

public void SetAudioGain(float audio_gain) {
snowboyJNI.SnowboyVad_SetAudioGain(swigCPtr, this, audio_gain);
}

public void ApplyFrontend(boolean apply_frontend) {
snowboyJNI.SnowboyVad_ApplyFrontend(swigCPtr, this, apply_frontend);
}

public int SampleRate() {
return snowboyJNI.SnowboyVad_SampleRate(swigCPtr, this);
}

public int NumChannels() {
return snowboyJNI.SnowboyVad_NumChannels(swigCPtr, this);
}

public int BitsPerSample() {
return snowboyJNI.SnowboyVad_BitsPerSample(swigCPtr, this);
}

}
28 changes: 28 additions & 0 deletions app/src/main/java/ai/kitt/snowboy/snowboy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* ----------------------------------------------------------------------------
* This file was automatically generated by SWIG (http://www.swig.org).
* Version 4.0.1
*
* Do not make changes to this file unless you know what you are doing--modify
* the SWIG interface file instead.
* ----------------------------------------------------------------------------- */

package ai.kitt.snowboy;

public class snowboy {
public enum MsgEnum {
MSG_VAD_END,
MSG_VAD_NOSPEECH,
MSG_VAD_SPEECH,
MSG_VOLUME_NOTIFY,
MSG_WAV_DATAINFO,
MSG_RECORD_START,
MSG_RECORD_STOP,
MSG_ACTIVE,
MSG_ERROR,
MSG_INFO;

public static MsgEnum getMsgEnum(int i) {
return MsgEnum.values()[i];
}
}
}
Loading