Compare commits

...

4 Commits

Author SHA1 Message Date
thisisAcidic
d1933c3b67 android: add popup toggles (#561)
* android: add toggles to disable bottom sheet and dynamic island popups

* android: translations for popup customization (de, es, fr, pt)
2026-05-05 12:48:22 +05:30
thisisAcidic
fb44f01ac0 android: allow non-premium users to disable head gestures (#564) 2026-05-03 01:41:23 +05:30
thisisAcidic
93a93cbe68 fix: sync magisk update json with current release URLs (#563) 2026-05-03 00:59:43 +05:30
Nikhil Maddirala
a4898293b8 docs: update readme root requirements (#557)
* Update readme root requirements

Clarified root requirements for LibrePods depending on device/OS and features needed.

* Revise Xposed workaround note in README

Updated warning about Xposed/LSPosed workaround for compatibility.
2026-05-01 20:06:42 +05:30
11 changed files with 99 additions and 14 deletions

View File

@@ -76,19 +76,15 @@ https://github.com/user-attachments/assets/43911243-0576-4093-8c55-89c1db5ea533
### Root Requirement ### Root Requirement
The app needs root because of a bug in the Android Bluetooth stack Fluoride/non-compliance of Apple with Bluetooth standards. You must have Xposed installed for the app to workaround this bug and connect to AirPods. LibrePods **may** require root depending on your device/OS and what features you want access to:
[https://issuetracker.google.com/issues/371713238](https://issuetracker.google.com/issues/371713238) - Features requiring the VendorID hook ([the features marked with an asterisk here](https://github.com/kavishdevar/librepods#key-features)) will always require root regardless of your device/OS.
- On **ColorOS/OxygenOS 16** and **Pixel devices on Android 16 QPR3** (with the latest Google Play system update), LibrePods does not need root for most features (except those requiring the VendorID hook mentioned above).
Please do not comment in the thread. The issue has already been resolved and should be available in Android 17 for all devices. - On other devices, LibrePods needs root because of a bug in the Android Bluetooth stack Fluoride/non-compliance of Apple with Bluetooth standards. You must have Xposed installed for the app to workaround this bug and connect to AirPods. [This issue is being tracked here](https://issuetracker.google.com/issues/371713238). Please do not comment on the issue thread. The issue has already been resolved and should be available in **Android 17** for all devices.
However, if you are using ColorOS/OxygenOS 16, Android 16 QPR3 on Pixel (ensure you're on the latest Play system update), you don't need root for most features.
> [!IMPORTANT] > [!IMPORTANT]
> This workaround with Xposed is not guaranteed to work on all devices. > This workaround with Xposed is not guaranteed to work on all devices.
Features requiring the VendorID hook will still require root. These features include customizing transparency mode, setting up hearing aid, and use Bluetooth Multipoint.
### Troubleshooting steps for common errors ### Troubleshooting steps for common errors
- Ensure the correct scope is set in LSPosed/Vector. - Ensure the correct scope is set in LSPosed/Vector.
- Ensure there is no root-hiding module preventing the hook from loading on the Bluetooth app. - Ensure there is no root-hiding module preventing the hook from loading on the Bluetooth app.

View File

@@ -157,6 +157,48 @@ fun AppSettingsScreen(
enabled = state.isPremium enabled = state.isPremium
) )
Text(
text = stringResource(R.string.popup_animations), style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = textColor.copy(alpha = 0.6f),
fontFamily = FontFamily(Font(R.font.sf_pro))
), modifier = Modifier.padding(16.dp, bottom = 2.dp, top = 24.dp)
)
Spacer(modifier = Modifier.height(2.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.background(
backgroundColor, RoundedCornerShape(28.dp)
)
.padding(vertical = 4.dp)
) {
StyledToggle(
label = stringResource(R.string.show_bottom_sheet_popup),
description = stringResource(R.string.show_bottom_sheet_popup_description),
checked = state.showBottomSheetPopup,
onCheckedChange = viewModel::setShowBottomSheetPopup,
independent = false
)
HorizontalDivider(
thickness = 1.dp,
color = Color(0x40888888),
modifier = Modifier.padding(horizontal = 12.dp)
)
StyledToggle(
label = stringResource(R.string.show_island_popup),
description = stringResource(R.string.show_island_popup_description),
checked = state.showIslandPopup,
onCheckedChange = viewModel::setShowIslandPopup,
independent = false
)
}
Text( Text(
text = stringResource(R.string.conversational_awareness), style = TextStyle( text = stringResource(R.string.conversational_awareness), style = TextStyle(
fontSize = 14.sp, fontSize = 14.sp,

View File

@@ -197,7 +197,7 @@ fun HeadTrackingScreen(viewModel: AirPodsViewModel, navController: NavController
label = "Head Gestures", label = "Head Gestures",
checked = state.headGesturesEnabled, checked = state.headGesturesEnabled,
onCheckedChange = { viewModel.setHeadGesturesEnabled(it) }, onCheckedChange = { viewModel.setHeadGesturesEnabled(it) },
enabled = state.isPremium, enabled = state.isPremium || state.headGesturesEnabled,
description = stringResource(R.string.head_gestures_details) description = stringResource(R.string.head_gestures_details)
) )

View File

@@ -32,7 +32,9 @@ data class AppSettingsUiState(
val cameraPackageError: String? = null, val cameraPackageError: String? = null,
val vendorIdHook: Boolean = false, val vendorIdHook: Boolean = false,
val isPremium: Boolean = false, val isPremium: Boolean = false,
val connectionSuccessful: Boolean = false val connectionSuccessful: Boolean = false,
val showBottomSheetPopup: Boolean = true,
val showIslandPopup: Boolean = true
) )
class AppSettingsViewModel(application: Application) : AndroidViewModel(application) { class AppSettingsViewModel(application: Application) : AndroidViewModel(application) {
@@ -86,7 +88,9 @@ class AppSettingsViewModel(application: Application) : AndroidViewModel(applicat
conversationalAwarenessVolume = sharedPreferences.getInt("conversational_awareness_volume", 43).toFloat(), conversationalAwarenessVolume = sharedPreferences.getInt("conversational_awareness_volume", 43).toFloat(),
cameraPackageValue = sharedPreferences.getString("custom_camera_package", "") ?: "", cameraPackageValue = sharedPreferences.getString("custom_camera_package", "") ?: "",
vendorIdHook = xposedRemotePref.getBoolean("vendor_id_hook", false), vendorIdHook = xposedRemotePref.getBoolean("vendor_id_hook", false),
connectionSuccessful = sharedPreferences.getBoolean("connection_successful", false) connectionSuccessful = sharedPreferences.getBoolean("connection_successful", false),
showBottomSheetPopup = sharedPreferences.getBoolean("show_bottom_sheet_popup", true),
showIslandPopup = sharedPreferences.getBoolean("show_island_popup", true)
) )
} }
} }
@@ -176,4 +180,14 @@ class AppSettingsViewModel(application: Application) : AndroidViewModel(applicat
xposedRemotePref.putBoolean("vendor_id_hook", enabled) xposedRemotePref.putBoolean("vendor_id_hook", enabled)
_uiState.update { it.copy(vendorIdHook = enabled) } _uiState.update { it.copy(vendorIdHook = enabled) }
} }
fun setShowBottomSheetPopup(enabled: Boolean) {
sharedPreferences.edit { putBoolean("show_bottom_sheet_popup", enabled) }
_uiState.update { it.copy(showBottomSheetPopup = enabled) }
}
fun setShowIslandPopup(enabled: Boolean) {
sharedPreferences.edit { putBoolean("show_island_popup", enabled) }
_uiState.update { it.copy(showIslandPopup = enabled) }
}
} }

View File

@@ -1636,6 +1636,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
var popupShown = false var popupShown = false
fun showPopup(service: Service, name: String) { fun showPopup(service: Service, name: String) {
if (!sharedPreferences.getBoolean("show_bottom_sheet_popup", true)) {
return
}
if (!Settings.canDrawOverlays(service)) { if (!Settings.canDrawOverlays(service)) {
Log.d(TAG, "No permission for SYSTEM_ALERT_WINDOW") Log.d(TAG, "No permission for SYSTEM_ALERT_WINDOW")
return return
@@ -1660,6 +1663,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
otherDeviceName: String? = null otherDeviceName: String? = null
) { ) {
Log.d(TAG, "Showing island window") Log.d(TAG, "Showing island window")
if (!sharedPreferences.getBoolean("show_island_popup", true)) {
return
}
if (!Settings.canDrawOverlays(service)) { if (!Settings.canDrawOverlays(service)) {
Log.d(TAG, "No permission for SYSTEM_ALERT_WINDOW") Log.d(TAG, "No permission for SYSTEM_ALERT_WINDOW")
return return

View File

@@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="popup_animations">Popup-Animationen</string>
<string name="show_bottom_sheet_popup">Popup unten</string>
<string name="show_bottom_sheet_popup_description">Zeigt das Popup im iOS-Stil unten an, wenn AirPods sich verbinden.</string>
<string name="show_island_popup">Dynamic Island Popup</string>
<string name="show_island_popup_description">Zeigt das Popup im Dynamic-Island-Stil oben für Verbindungs- und Übergabe-Ereignisse.</string>
</resources>

View File

@@ -210,4 +210,9 @@
<string name="listening_mode_transparency_description">Deja entrar los sonidos externos</string> <string name="listening_mode_transparency_description">Deja entrar los sonidos externos</string>
<string name="listening_mode_adaptive_description">Ajuste dinámico del ruido externo</string> <string name="listening_mode_adaptive_description">Ajuste dinámico del ruido externo</string>
<string name="listening_mode_noise_cancellation_description">Bloquea los sonidos externos</string> <string name="listening_mode_noise_cancellation_description">Bloquea los sonidos externos</string>
<string name="popup_animations">Animaciones emergentes</string>
<string name="show_bottom_sheet_popup">Ventana emergente inferior</string>
<string name="show_bottom_sheet_popup_description">Muestra la ventana emergente estilo iOS en la parte inferior cuando los AirPods se conectan.</string>
<string name="show_island_popup">Ventana emergente Dynamic Island</string>
<string name="show_island_popup_description">Muestra la ventana emergente estilo Dynamic Island en la parte superior para eventos de conexión y traspaso.</string>
</resources> </resources>

View File

@@ -210,4 +210,9 @@
<string name="listening_mode_transparency_description">Laisser entrer les sons extérieurs</string> <string name="listening_mode_transparency_description">Laisser entrer les sons extérieurs</string>
<string name="listening_mode_adaptive_description">Ajuster dynamiquement les sons extérieurs</string> <string name="listening_mode_adaptive_description">Ajuster dynamiquement les sons extérieurs</string>
<string name="listening_mode_noise_cancellation_description">Bloquer les sons extérieurs</string> <string name="listening_mode_noise_cancellation_description">Bloquer les sons extérieurs</string>
<string name="popup_animations">Animations contextuelles</string>
<string name="show_bottom_sheet_popup">Fenêtre contextuelle en bas</string>
<string name="show_bottom_sheet_popup_description">Afficher la fenêtre contextuelle de style iOS en bas de l\'écran lors de la connexion des AirPods.</string>
<string name="show_island_popup">Fenêtre Dynamic Island</string>
<string name="show_island_popup_description">Afficher la fenêtre de style Dynamic Island en haut de l\'écran pour les événements de connexion et de transfert.</string>
</resources> </resources>

View File

@@ -210,4 +210,9 @@
<string name="listening_mode_transparency_description">Permite sons externos</string> <string name="listening_mode_transparency_description">Permite sons externos</string>
<string name="listening_mode_adaptive_description">Ajusta dinamicamente o ruído externo</string> <string name="listening_mode_adaptive_description">Ajusta dinamicamente o ruído externo</string>
<string name="listening_mode_noise_cancellation_description">Bloqueia sons externos</string> <string name="listening_mode_noise_cancellation_description">Bloqueia sons externos</string>
<string name="popup_animations">Animações de pop-up</string>
<string name="show_bottom_sheet_popup">Pop-up inferior</string>
<string name="show_bottom_sheet_popup_description">Exibe o pop-up estilo iOS na parte inferior quando os AirPods se conectam.</string>
<string name="show_island_popup">Pop-up Dynamic Island</string>
<string name="show_island_popup_description">Exibe o pop-up estilo Dynamic Island no topo da tela em eventos de conexão e transferência.</string>
</resources> </resources>

View File

@@ -140,6 +140,11 @@
<string name="widget">Widget</string> <string name="widget">Widget</string>
<string name="show_phone_battery_in_widget">Show phone battery in widget</string> <string name="show_phone_battery_in_widget">Show phone battery in widget</string>
<string name="show_phone_battery_in_widget_description">Display your phone\'s battery level in the widget alongside AirPods battery</string> <string name="show_phone_battery_in_widget_description">Display your phone\'s battery level in the widget alongside AirPods battery</string>
<string name="popup_animations">Popup Animations</string>
<string name="show_bottom_sheet_popup">Bottom sheet popup</string>
<string name="show_bottom_sheet_popup_description">Show the iOS-style modal popup at the bottom when AirPods connect.</string>
<string name="show_island_popup">Dynamic Island popup</string>
<string name="show_island_popup_description">Show the Dynamic Island-style popup at the top for connection and takeover events.</string>
<string name="conversational_awareness_volume">Conversational Awareness Volume</string> <string name="conversational_awareness_volume">Conversational Awareness Volume</string>
<string name="quick_settings_tile">Quick Settings Tile</string> <string name="quick_settings_tile">Quick Settings Tile</string>
<string name="open_dialog_for_controlling">Open dialog for controlling</string> <string name="open_dialog_for_controlling">Open dialog for controlling</string>

View File

@@ -1,6 +1,6 @@
{ {
"version": "v0.2.6", "version": "v0.2.6",
"versionCode": 46, "versionCode": 46,
"zipUrl": "https://github.com/kavishdevar/librepods/releases/download/v0.2.3/LibrePods-FOSS-v0.2.3-release.zip", "zipUrl": "https://github.com/kavishdevar/librepods/releases/download/v0.2.6/LibrePods-FOSS-v0.2.6-release.zip",
"changelog": "https://raw.githubusercontent.com/kavishdevar/librepods/main/CHANGELOG.md" "changelog": "https://raw.githubusercontent.com/kavishdevar/librepods/main/extras/CHANGELOG.md"
} }