From dc6a89c62c1ecd17c218b02b3085ba134639ca9e Mon Sep 17 00:00:00 2001 From: thisisAcidic Date: Sat, 2 May 2026 23:06:54 +0200 Subject: [PATCH 1/4] android: scale noise control widget icons to fit available height --- .../main/res/layout/noise_control_widget.xml | 97 ++++++++++++------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/android/app/src/main/res/layout/noise_control_widget.xml b/android/app/src/main/res/layout/noise_control_widget.xml index baa2f4bdd..b35c27fb0 100644 --- a/android/app/src/main/res/layout/noise_control_widget.xml +++ b/android/app/src/main/res/layout/noise_control_widget.xml @@ -18,61 +18,77 @@ android:id="@+id/widget_off_button" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginVertical="12dp" + android:layout_marginVertical="6dp" android:layout_marginStart="12dp" android:layout_marginEnd="2dp" android:layout_weight="1" android:background="@drawable/widget_button_shape_start" android:clickable="true" android:gravity="center" - android:orientation="vertical"> + android:orientation="vertical" + android:padding="4dp"> + android:textSize="11sp" /> + android:orientation="vertical" + android:padding="4dp"> @@ -80,32 +96,39 @@ android:id="@+id/widget_adaptive_button" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginVertical="12dp" + android:layout_marginVertical="6dp" android:layout_marginStart="2dp" android:layout_marginEnd="2dp" android:layout_weight="1" android:background="@drawable/widget_button_shape_middle" android:clickable="true" android:gravity="center" - android:orientation="vertical"> + android:orientation="vertical" + android:padding="4dp"> @@ -113,31 +136,39 @@ android:id="@+id/widget_anc_button" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginVertical="12dp" + android:layout_marginVertical="6dp" android:layout_marginStart="2dp" android:layout_marginEnd="12dp" android:layout_weight="1" android:background="@drawable/widget_button_shape_end" android:clickable="true" android:gravity="center" - android:orientation="vertical"> + android:orientation="vertical" + android:padding="4dp"> From 45d1fa58603bfac1b9f6c019d8f3bc19a1b82877 Mon Sep 17 00:00:00 2001 From: thisisAcidic Date: Sun, 3 May 2026 01:05:12 +0200 Subject: [PATCH 2/4] android: replace existing control command status entry instead of leaving the stale one in the list --- .../main/java/me/kavishdevar/librepods/bluetooth/AACPManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/bluetooth/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/bluetooth/AACPManager.kt index 726430ea1..22288c4fc 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/bluetooth/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/bluetooth/AACPManager.kt @@ -207,7 +207,7 @@ class AACPManager { identifier: ControlCommandIdentifiers, value: ByteArray ) { val existingStatus = getControlCommandStatus(identifier) - if (existingStatus?.value.contentEquals(value)) { + if (existingStatus != null) { controlCommandStatusList.remove(existingStatus) } controlCommandListeners[identifier]?.forEach { listener -> From 30c315a6411166caa72cd466d283201b8cb85967 Mon Sep 17 00:00:00 2001 From: thisisAcidic Date: Sun, 3 May 2026 01:05:52 +0200 Subject: [PATCH 3/4] android: refresh noise control widget when Off Listening Mode pref changes --- .../java/me/kavishdevar/librepods/services/AirPodsService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index 464edddea..4f18237e7 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -1462,6 +1462,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList preferences.getBoolean(key, true) "head_gestures" -> config.headGestures = preferences.getBoolean(key, true) + "off_listening_mode" -> updateNoiseControlWidget() "disconnect_when_not_wearing" -> config.disconnectWhenNotWearing = preferences.getBoolean(key, false) From edf5701eec8afdee5075e68c422954995e16e21e Mon Sep 17 00:00:00 2001 From: thisisAcidic Date: Sun, 3 May 2026 01:27:21 +0200 Subject: [PATCH 4/4] android: keep noise control widget labels uniform in size and abbreviate ANC button --- .../librepods/services/AirPodsService.kt | 127 ++++++++++++------ .../main/res/layout/noise_control_widget.xml | 18 +-- android/app/src/main/res/values/strings.xml | 1 + 3 files changed, 91 insertions(+), 55 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index 4f18237e7..f163acc39 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -1958,54 +1958,97 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList val appWidgetManager = AppWidgetManager.getInstance(this) val componentName = ComponentName(this, NoiseControlWidget::class.java) val widgetIds = appWidgetManager.getAppWidgetIds(componentName) - val remoteViews = RemoteViews(packageName, R.layout.noise_control_widget).also { it -> - val ancStatus = ancNotification.status - val allowOffModeValue = - aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION } - val allowOffMode = - allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte() || sharedPreferences.getBoolean("off_listening_mode", true) - it.setInt( - R.id.widget_off_button, - "setBackgroundResource", - if (ancStatus == 1) R.drawable.widget_button_checked_shape_start else R.drawable.widget_button_shape_start - ) - it.setInt( - R.id.widget_transparency_button, - "setBackgroundResource", - if (ancStatus == 3) (if (allowOffMode) R.drawable.widget_button_checked_shape_middle else R.drawable.widget_button_checked_shape_start) else (if (allowOffMode) R.drawable.widget_button_shape_middle else R.drawable.widget_button_shape_start) - ) - it.setInt( - R.id.widget_adaptive_button, - "setBackgroundResource", - if (ancStatus == 4) R.drawable.widget_button_checked_shape_middle else R.drawable.widget_button_shape_middle - ) - it.setInt( - R.id.widget_anc_button, - "setBackgroundResource", - if (ancStatus == 2) R.drawable.widget_button_checked_shape_end else R.drawable.widget_button_shape_end - ) - it.setViewVisibility( - R.id.widget_off_button, if (allowOffMode) View.VISIBLE else View.GONE - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - it.setViewLayoutMargin( - R.id.widget_transparency_button, - RemoteViews.MARGIN_START, - if (allowOffMode) 2f else 12f, - TypedValue.COMPLEX_UNIT_DIP + + val ancStatus = ancNotification.status + val allowOffModeValue = + aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION } + val allowOffMode = + allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte() || sharedPreferences.getBoolean("off_listening_mode", true) + + val visibleLabels = buildList { + if (allowOffMode) add(getString(R.string.off)) + add(getString(R.string.transparency)) + add(getString(R.string.adaptive)) + add(getString(R.string.widget_noise_cancellation)) + } + + for (widgetId in widgetIds) { + val widgetWidthDp = appWidgetManager.getAppWidgetOptions(widgetId) + .getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 180) + val uniformTextSizeSp = computeUniformWidgetTextSizeSp(widgetWidthDp, visibleLabels.size, visibleLabels) + + val remoteViews = RemoteViews(packageName, R.layout.noise_control_widget).also { it -> + it.setInt( + R.id.widget_off_button, + "setBackgroundResource", + if (ancStatus == 1) R.drawable.widget_button_checked_shape_start else R.drawable.widget_button_shape_start ) - } else { - it.setViewPadding( + it.setInt( R.id.widget_transparency_button, - if (allowOffMode) 2.dpToPx() else 12.dpToPx(), - 12.dpToPx(), - 2.dpToPx(), - 12.dpToPx() + "setBackgroundResource", + if (ancStatus == 3) (if (allowOffMode) R.drawable.widget_button_checked_shape_middle else R.drawable.widget_button_checked_shape_start) else (if (allowOffMode) R.drawable.widget_button_shape_middle else R.drawable.widget_button_shape_start) ) + it.setInt( + R.id.widget_adaptive_button, + "setBackgroundResource", + if (ancStatus == 4) R.drawable.widget_button_checked_shape_middle else R.drawable.widget_button_shape_middle + ) + it.setInt( + R.id.widget_anc_button, + "setBackgroundResource", + if (ancStatus == 2) R.drawable.widget_button_checked_shape_end else R.drawable.widget_button_shape_end + ) + it.setViewVisibility( + R.id.widget_off_button, if (allowOffMode) View.VISIBLE else View.GONE + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + it.setViewLayoutMargin( + R.id.widget_transparency_button, + RemoteViews.MARGIN_START, + if (allowOffMode) 2f else 12f, + TypedValue.COMPLEX_UNIT_DIP + ) + } else { + it.setViewPadding( + R.id.widget_transparency_button, + if (allowOffMode) 2.dpToPx() else 12.dpToPx(), + 12.dpToPx(), + 2.dpToPx(), + 12.dpToPx() + ) + } + it.setTextViewTextSize(R.id.widget_off_button_label, TypedValue.COMPLEX_UNIT_SP, uniformTextSizeSp) + it.setTextViewTextSize(R.id.widget_transparency_button_label, TypedValue.COMPLEX_UNIT_SP, uniformTextSizeSp) + it.setTextViewTextSize(R.id.widget_adaptive_button_label, TypedValue.COMPLEX_UNIT_SP, uniformTextSizeSp) + it.setTextViewTextSize(R.id.widget_anc_button_label, TypedValue.COMPLEX_UNIT_SP, uniformTextSizeSp) } + + appWidgetManager.updateAppWidget(widgetId, remoteViews) } + } - appWidgetManager.updateAppWidget(widgetIds, remoteViews) + private fun computeUniformWidgetTextSizeSp( + widgetWidthDp: Int, + buttonCount: Int, + labels: List + ): Float { + val displayMetrics = resources.displayMetrics + val widgetWidthPx = widgetWidthDp * displayMetrics.density + val outerMarginsPx = 24f * displayMetrics.density + val gapsPx = (buttonCount - 1) * 4f * displayMetrics.density + val perButtonPaddingPx = 8f * displayMetrics.density + val availablePerButtonPx = (widgetWidthPx - outerMarginsPx - gapsPx) / buttonCount - perButtonPaddingPx + + val paint = android.text.TextPaint() + var size = 11f + while (size >= 7f) { + paint.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, size, displayMetrics) + if (labels.all { paint.measureText(it) <= availablePerButtonPx }) { + return size + } + size -= 0.5f + } + return 7f } @OptIn(ExperimentalMaterial3Api::class) diff --git a/android/app/src/main/res/layout/noise_control_widget.xml b/android/app/src/main/res/layout/noise_control_widget.xml index b35c27fb0..e1981838b 100644 --- a/android/app/src/main/res/layout/noise_control_widget.xml +++ b/android/app/src/main/res/layout/noise_control_widget.xml @@ -38,11 +38,9 @@ android:tint="@color/white" /> diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 3a1db0b77..6fa31a8a8 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -267,4 +267,5 @@ App enabled in Xposed Subject Describe your issue + ANC