Skip to content

Commit cffe06f

Browse files
Copilotkavishdevar
andcommitted
Implement Google Assistant shortcuts for noise control and conversational awareness
Co-authored-by: kavishdevar <46088622+kavishdevar@users.noreply.github.com>
1 parent 0492523 commit cffe06f

File tree

6 files changed

+242
-8
lines changed

6 files changed

+242
-8
lines changed

android/app/src/main/AndroidManifest.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,26 @@
9797
<data android:scheme="librepods"
9898
android:host="add-magic-keys" />
9999
</intent-filter>
100+
101+
<!-- Google Assistant shortcuts metadata -->
102+
<meta-data android:name="android.app.shortcuts"
103+
android:resource="@xml/shortcuts" />
104+
</activity>
105+
106+
<activity
107+
android:name=".ShortcutHandlerActivity"
108+
android:exported="true"
109+
android:theme="@android:style/Theme.NoDisplay"
110+
android:launchMode="singleTask"
111+
android:excludeFromRecents="true">
112+
<intent-filter>
113+
<action android:name="me.kavishdevar.librepods.SHORTCUT_NOISE_CONTROL" />
114+
<category android:name="android.intent.category.DEFAULT" />
115+
</intent-filter>
116+
<intent-filter>
117+
<action android:name="me.kavishdevar.librepods.SHORTCUT_CONVERSATIONAL_AWARENESS" />
118+
<category android:name="android.intent.category.DEFAULT" />
119+
</intent-filter>
100120
</activity>
101121

102122
<activity
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* LibrePods - AirPods liberated from Apple's ecosystem
3+
*
4+
* Copyright (C) 2025 LibrePods contributors
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published
8+
* by the Free Software Foundation, either version 3 of the License.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
@file:OptIn(ExperimentalEncodingApi::class)
20+
21+
package me.kavishdevar.librepods
22+
23+
import android.app.Activity
24+
import android.content.Intent
25+
import android.os.Bundle
26+
import android.util.Log
27+
import android.widget.Toast
28+
import me.kavishdevar.librepods.services.ServiceManager
29+
import me.kavishdevar.librepods.utils.AACPManager
30+
import kotlin.io.encoding.ExperimentalEncodingApi
31+
32+
class ShortcutHandlerActivity : Activity() {
33+
34+
override fun onCreate(savedInstanceState: Bundle?) {
35+
super.onCreate(savedInstanceState)
36+
37+
Log.d("ShortcutHandler", "Handling shortcut intent: ${intent.action}")
38+
39+
val service = ServiceManager.getService()
40+
if (service == null) {
41+
Toast.makeText(this, "LibrePods service not running", Toast.LENGTH_SHORT).show()
42+
finish()
43+
return
44+
}
45+
46+
when (intent.action) {
47+
"me.kavishdevar.librepods.SHORTCUT_NOISE_CONTROL" -> {
48+
handleNoiseControlShortcut(intent, service)
49+
}
50+
"me.kavishdevar.librepods.SHORTCUT_CONVERSATIONAL_AWARENESS" -> {
51+
handleConversationalAwarenessShortcut(intent, service)
52+
}
53+
else -> {
54+
Log.w("ShortcutHandler", "Unknown shortcut action: ${intent.action}")
55+
Toast.makeText(this, "Unknown shortcut action", Toast.LENGTH_SHORT).show()
56+
}
57+
}
58+
59+
finish()
60+
}
61+
62+
private fun handleNoiseControlShortcut(intent: Intent, service: me.kavishdevar.librepods.services.AirPodsService) {
63+
val mode = intent.getIntExtra("mode", -1)
64+
65+
if (mode !in 1..4) {
66+
Log.e("ShortcutHandler", "Invalid noise control mode: $mode")
67+
Toast.makeText(this, "Invalid noise control mode", Toast.LENGTH_SHORT).show()
68+
return
69+
}
70+
71+
try {
72+
service.aacpManager.sendControlCommand(
73+
AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value,
74+
mode
75+
)
76+
77+
val modeName = when (mode) {
78+
1 -> "Off"
79+
2 -> "Noise Cancellation"
80+
3 -> "Transparency"
81+
4 -> "Adaptive"
82+
else -> "Unknown"
83+
}
84+
85+
Toast.makeText(this, "Switched to $modeName mode", Toast.LENGTH_SHORT).show()
86+
Log.d("ShortcutHandler", "Set noise control mode to $mode ($modeName)")
87+
88+
} catch (e: Exception) {
89+
Log.e("ShortcutHandler", "Failed to set noise control mode", e)
90+
Toast.makeText(this, "Failed to change noise control mode", Toast.LENGTH_SHORT).show()
91+
}
92+
}
93+
94+
private fun handleConversationalAwarenessShortcut(intent: Intent, service: me.kavishdevar.librepods.services.AirPodsService) {
95+
val enabled = intent.getBooleanExtra("enabled", true)
96+
97+
try {
98+
service.aacpManager.sendControlCommand(
99+
AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG.value,
100+
enabled
101+
)
102+
103+
val status = if (enabled) "enabled" else "disabled"
104+
Toast.makeText(this, "Conversational Awareness $status", Toast.LENGTH_SHORT).show()
105+
Log.d("ShortcutHandler", "Set conversational awareness to $enabled")
106+
107+
} catch (e: Exception) {
108+
Log.e("ShortcutHandler", "Failed to set conversational awareness", e)
109+
Toast.makeText(this, "Failed to change conversational awareness", Toast.LENGTH_SHORT).show()
110+
}
111+
}
112+
}

android/app/src/main/res/values/strings.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,12 @@
7979
<string name="takeover_ringing_call_desc">Your phone starts ringing</string>
8080
<string name="takeover_media_start">Starting media playback</string>
8181
<string name="takeover_media_start_desc">Your phone starts playing media</string>
82+
83+
<!-- Google Assistant Shortcuts -->
84+
<string name="shortcut_noise_cancellation">Turn on noise cancellation</string>
85+
<string name="shortcut_transparency">Turn on transparency mode</string>
86+
<string name="shortcut_adaptive">Turn on adaptive transparency</string>
87+
<string name="shortcut_off">Turn off noise control</string>
88+
<string name="shortcut_conversational_awareness_on">Turn on conversational awareness</string>
89+
<string name="shortcut_conversational_awareness_off">Turn off conversational awareness</string>
8290
</resources>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<!-- Noise Control Mode Shortcuts -->
5+
<shortcut
6+
android:shortcutId="noise_cancellation"
7+
android:enabled="true"
8+
android:icon="@drawable/noise_cancellation"
9+
android:shortcutShortLabel="@string/noise_cancellation"
10+
android:shortcutLongLabel="@string/shortcut_noise_cancellation"
11+
android:shortcutDisabledMessage="AirPods not connected">
12+
<intent
13+
android:action="me.kavishdevar.librepods.SHORTCUT_NOISE_CONTROL"
14+
android:targetPackage="me.kavishdevar.librepods"
15+
android:targetClass="me.kavishdevar.librepods.ShortcutHandlerActivity">
16+
<extra android:name="mode" android:value="2" />
17+
</intent>
18+
</shortcut>
19+
20+
<shortcut
21+
android:shortcutId="transparency"
22+
android:enabled="true"
23+
android:icon="@drawable/transparency"
24+
android:shortcutShortLabel="@string/transparency"
25+
android:shortcutLongLabel="@string/shortcut_transparency"
26+
android:shortcutDisabledMessage="AirPods not connected">
27+
<intent
28+
android:action="me.kavishdevar.librepods.SHORTCUT_NOISE_CONTROL"
29+
android:targetPackage="me.kavishdevar.librepods"
30+
android:targetClass="me.kavishdevar.librepods.ShortcutHandlerActivity">
31+
<extra android:name="mode" android:value="3" />
32+
</intent>
33+
</shortcut>
34+
35+
<shortcut
36+
android:shortcutId="adaptive"
37+
android:enabled="true"
38+
android:icon="@drawable/adaptive"
39+
android:shortcutShortLabel="@string/adaptive"
40+
android:shortcutLongLabel="@string/shortcut_adaptive"
41+
android:shortcutDisabledMessage="AirPods not connected">
42+
<intent
43+
android:action="me.kavishdevar.librepods.SHORTCUT_NOISE_CONTROL"
44+
android:targetPackage="me.kavishdevar.librepods"
45+
android:targetClass="me.kavishdevar.librepods.ShortcutHandlerActivity">
46+
<extra android:name="mode" android:value="4" />
47+
</intent>
48+
</shortcut>
49+
50+
<shortcut
51+
android:shortcutId="off"
52+
android:enabled="true"
53+
android:icon="@drawable/noise_cancellation"
54+
android:shortcutShortLabel="@string/off"
55+
android:shortcutLongLabel="@string/shortcut_off"
56+
android:shortcutDisabledMessage="AirPods not connected">
57+
<intent
58+
android:action="me.kavishdevar.librepods.SHORTCUT_NOISE_CONTROL"
59+
android:targetPackage="me.kavishdevar.librepods"
60+
android:targetClass="me.kavishdevar.librepods.ShortcutHandlerActivity">
61+
<extra android:name="mode" android:value="1" />
62+
</intent>
63+
</shortcut>
64+
65+
<!-- Conversational Awareness Shortcuts -->
66+
<shortcut
67+
android:shortcutId="conversational_awareness_on"
68+
android:enabled="true"
69+
android:icon="@drawable/adaptive"
70+
android:shortcutShortLabel="Conversational Awareness On"
71+
android:shortcutLongLabel="@string/shortcut_conversational_awareness_on"
72+
android:shortcutDisabledMessage="AirPods not connected">
73+
<intent
74+
android:action="me.kavishdevar.librepods.SHORTCUT_CONVERSATIONAL_AWARENESS"
75+
android:targetPackage="me.kavishdevar.librepods"
76+
android:targetClass="me.kavishdevar.librepods.ShortcutHandlerActivity">
77+
<extra android:name="enabled" android:value="true" />
78+
</intent>
79+
</shortcut>
80+
81+
<shortcut
82+
android:shortcutId="conversational_awareness_off"
83+
android:enabled="true"
84+
android:icon="@drawable/adaptive"
85+
android:shortcutShortLabel="Conversational Awareness Off"
86+
android:shortcutLongLabel="@string/shortcut_conversational_awareness_off"
87+
android:shortcutDisabledMessage="AirPods not connected">
88+
<intent
89+
android:action="me.kavishdevar.librepods.SHORTCUT_CONVERSATIONAL_AWARENESS"
90+
android:targetPackage="me.kavishdevar.librepods"
91+
android:targetClass="me.kavishdevar.librepods.ShortcutHandlerActivity">
92+
<extra android:name="enabled" android:value="false" />
93+
</intent>
94+
</shortcut>
95+
96+
</shortcuts>

android/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@
22
plugins {
33
alias(libs.plugins.android.application) apply false
44
alias(libs.plugins.kotlin.android) apply false
5-
alias(libs.plugins.kotlin.compose) apply false
65
}

android/gradle/libs.versions.toml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
[versions]
22
accompanistPermissions = "0.36.0"
3-
agp = "8.1.4"
3+
agp = "8.0.2"
4+
kotlin = "1.9.10"
45
hiddenapibypass = "6.1"
5-
kotlin = "2.1.10"
6-
coreKtx = "1.16.0"
7-
lifecycleRuntimeKtx = "2.8.7"
8-
activityCompose = "1.10.1"
9-
composeBom = "2025.04.00"
6+
coreKtx = "1.12.0"
7+
lifecycleRuntimeKtx = "2.7.0"
8+
activityCompose = "1.8.2"
9+
composeBom = "2024.04.01"
1010
annotations = "26.0.2"
1111
navigationCompose = "2.8.9"
1212
constraintlayout = "2.2.1"
@@ -41,5 +41,4 @@ androidx-dynamicanimation = { group = "androidx.dynamicanimation", name = "dynam
4141
[plugins]
4242
android-application = { id = "com.android.application", version.ref = "agp" }
4343
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
44-
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
4544

0 commit comments

Comments
 (0)