From 96429c658921135ba1949baf53453448fde27271 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Wed, 9 Oct 2024 15:33:43 +0800 Subject: [PATCH 1/3] [Audiosharing] Save user preferred primary headset to SettingsProvider In hysteresis mode, we will receive plenty of onReceiveStateChanged, e.g. play and pause music, system sounds... The onReceiveStateChanged with BIS >= 1, as a replacement of onSourceAdded, will trigger auto pick logic for primary headset. In some cases, when user change primary headset in Call audio section on audio sharing page under the hysteresis mode, the system sound will later trigger a onReceiveStateChanged with BIS >= 1, then the auto pick logic (always pick the earliest connected headset) is possible to override the user change. Thus here we have to save the user preferred primary headset in SettingsProvider and skip the auto pick if user has made changes. Test: atest Bug: 355222285 Flag: com.android.settingslib.flags.audio_sharing_hysteresis_mode_fix Change-Id: I5ccf743eb685509ffcc3c7a88051726c10fe2567 --- ...oSharingCallAudioPreferenceController.java | 2 +- ...udioSharingDevicePreferenceController.java | 2 +- .../AudioSharingDialogHandler.java | 2 +- .../audiosharing/AudioSharingUtils.java | 24 ++++++++++++++ ...SharingDevicePreferenceControllerTest.java | 33 +++++++++++++++++++ .../AudioSharingDialogHandlerTest.java | 31 +++++++++++++++++ 6 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java index 468ac3d5652..11a337fb616 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java @@ -219,7 +219,7 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP if (lead != null) { String addr = lead.getDevice().getAnonymizedAddress(); Log.d(TAG, "Set call audio device: " + addr); - lead.setActive(); + AudioSharingUtils.setPrimary(mContext, lead); logCallAudioDeviceChange(currentGroupId, lead); } else { Log.d(TAG, "Skip set call audio device: no lead"); diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java index db2c7b21b1b..47623e4f197 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java @@ -390,7 +390,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro Log.d(TAG, "onDeviceClick, set active in call mode"); CachedBluetoothDevice cachedDevice = ((BluetoothDevicePreference) preference).getBluetoothDevice(); - cachedDevice.setActive(); + AudioSharingUtils.setPrimary(mContext, cachedDevice); } mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUDIO_SHARING_DEVICE_CLICK, isCallMode); diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java index 396144ab16c..0c3448729b8 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java @@ -192,7 +192,7 @@ public class AudioSharingDialogHandler { // If this method is called with user triggered, e.g. manual click on the // "Connected devices" page, we need call setActive for the device, since user // intend to switch active device for the call. - cachedDevice.setActive(); + AudioSharingUtils.setPrimary(mContext, cachedDevice); } return; } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java index a662809fb78..592c8eb84c5 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java @@ -21,6 +21,7 @@ import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtil import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID; import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID; import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID; import static java.util.stream.Collectors.toList; @@ -28,6 +29,7 @@ import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.content.Context; +import android.provider.Settings; import android.util.Log; import android.util.Pair; import android.widget.Toast; @@ -44,6 +46,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.VolumeControlProfile; +import com.android.settingslib.flags.Flags; import java.util.ArrayList; import java.util.Comparator; @@ -344,6 +347,27 @@ public class AudioSharingUtils { return vc != null && vc.isProfileReady(); } + /** Set {@link CachedBluetoothDevice} as primary device for call audio */ + public static void setPrimary(@NonNull Context context, + @Nullable CachedBluetoothDevice cachedDevice) { + if (cachedDevice == null) return; + cachedDevice.setActive(); + if (Flags.audioSharingHysteresisModeFix()) { + int groupId = BluetoothUtils.getGroupId(cachedDevice); + // TODO: use real key name in SettingsProvider + int userPreferredId = Settings.Secure.getInt( + context.getContentResolver(), + BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + if (groupId != userPreferredId) { + Settings.Secure.putInt( + context.getContentResolver(), + BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, + groupId); + } + } + } + /** * Build audio sharing dialog log event data * diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceControllerTest.java index 61bc8aaa055..0bc0b949193 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceControllerTest.java @@ -18,6 +18,7 @@ package com.android.settings.connecteddevice.audiosharing; import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BLUETOOTH_DEVICE; import static com.google.common.truth.Truth.assertThat; @@ -37,6 +38,7 @@ import static org.robolectric.Shadows.shadowOf; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothLeBroadcastMetadata; @@ -50,6 +52,7 @@ import android.media.AudioManager; import android.os.Bundle; import android.os.Looper; import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; import android.util.Pair; import androidx.annotation.NonNull; @@ -587,6 +590,10 @@ public class AudioSharingDevicePreferenceControllerTest { @Test public void testInCallState_showCallStateTitleAndSetActiveOnDeviceClick() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + Settings.Secure.putInt(mContext.getContentResolver(), + BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); mController.displayPreference(mScreen); mAudioManager.setMode(AudioManager.MODE_IN_CALL); @@ -599,6 +606,32 @@ public class AudioSharingDevicePreferenceControllerTest { BluetoothDevicePreference preference = createBluetoothDevicePreference(); mController.onDeviceClick(preference); verify(mCachedDevice).setActive(); + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, + BluetoothCsipSetCoordinator.GROUP_ID_INVALID)).isEqualTo( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + } + + @Test + public void testInCallState_enableHysteresisFix_setAndSaveActiveOnDeviceClick() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + Settings.Secure.putInt(mContext.getContentResolver(), + BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + mController.displayPreference(mScreen); + + mAudioManager.setMode(AudioManager.MODE_IN_CALL); + mController.onAudioModeChanged(); + shadowOf(Looper.getMainLooper()).idle(); + + BluetoothDevicePreference preference = createBluetoothDevicePreference(); + when(mCachedDevice.getGroupId()).thenReturn(1); + mController.onDeviceClick(preference); + verify(mCachedDevice).setActive(); + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, + BluetoothCsipSetCoordinator.GROUP_ID_INVALID)).isEqualTo(1); } @Test diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java index ad6dd7f0754..c96a08623f8 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java @@ -16,6 +16,8 @@ package com.android.settings.connecteddevice.audiosharing; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -32,6 +34,7 @@ import static org.robolectric.Shadows.shadowOf; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcast; import android.bluetooth.BluetoothLeBroadcastMetadata; @@ -43,6 +46,7 @@ import android.media.AudioManager; import android.os.Bundle; import android.os.Looper; import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; import android.util.Pair; import androidx.fragment.app.DialogFragment; @@ -193,6 +197,10 @@ public class AudioSharingDialogHandlerTest { @Test public void handleUserTriggeredDeviceConnected_inCall_setActive() { + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + Settings.Secure.putInt(mContext.getContentResolver(), + BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); setUpBroadcast(true); ImmutableList deviceList = ImmutableList.of(mDevice1); @@ -201,6 +209,29 @@ public class AudioSharingDialogHandlerTest { mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ true); shadowOf(Looper.getMainLooper()).idle(); verify(mCachedDevice1).setActive(); + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, + BluetoothCsipSetCoordinator.GROUP_ID_INVALID)).isEqualTo( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + } + + @Test + public void handleUserTriggeredDeviceConnected_inCall_enableHysteresisFix_setAndSaveActive() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + Settings.Secure.putInt(mContext.getContentResolver(), + BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); + setUpBroadcast(true); + ImmutableList deviceList = ImmutableList.of(mDevice1); + when(mAssistant.getAllConnectedDevices()).thenReturn(deviceList); + when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of()); + mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ true); + shadowOf(Looper.getMainLooper()).idle(); + verify(mCachedDevice1).setActive(); + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, + BluetoothCsipSetCoordinator.GROUP_ID_INVALID)).isEqualTo(1); } @Test From e3c4db5884e814e8a3159a238ca499c1ec570fed Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Thu, 10 Oct 2024 02:33:20 +0000 Subject: [PATCH 2/3] Rename system caption "Text size" to "Caption size". Change-Id: I8ee4b4c5d3393827240b1c9f757efb0dc85d40e8 Fix: 295665940 Test: strings only Flag: EXEMPT resource only update --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 97a7e232304..c6d44c180db 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5639,7 +5639,7 @@ Language - Text size + Caption size Caption style From bab2edd0effa4ab9a81ae99c00eb35e57e0f7f8d Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Thu, 10 Oct 2024 13:11:27 +0800 Subject: [PATCH 3/3] Fix SpaSearchLandingActivity.isValidCall() SettingsIntelligence sometimes starts SearchResultTrampoline first, in this case, SearchResultTrampoline checks if the call is valid, then SearchResultTrampoline will start this SpaSearchLandingActivity, allow this use case. Fix: 370186204 Flag: EXEMPT bug fix Test: manual - search roaming Change-Id: Ib6b2c1d3b8754f478a637dda8e9df77a91b24d9e --- .../settings/spa/search/SpaSearchLandingActivity.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/spa/search/SpaSearchLandingActivity.kt b/src/com/android/settings/spa/search/SpaSearchLandingActivity.kt index 2c0955b42b0..b94f52c8d40 100644 --- a/src/com/android/settings/spa/search/SpaSearchLandingActivity.kt +++ b/src/com/android/settings/spa/search/SpaSearchLandingActivity.kt @@ -38,9 +38,17 @@ class SpaSearchLandingActivity : Activity() { finish() } - private fun isValidCall() = - PasswordUtils.getCallingAppPackageName(activityToken) == + private fun isValidCall(): Boolean { + val callingAppPackageName = PasswordUtils.getCallingAppPackageName(activityToken) + if (callingAppPackageName == packageName) { + // SettingsIntelligence sometimes starts SearchResultTrampoline first, in this case, + // SearchResultTrampoline checks if the call is valid, then SearchResultTrampoline will + // start this activity, allow this use case. + return true + } + return callingAppPackageName == featureFactory.searchFeatureProvider.getSettingsIntelligencePkgName(this) + } companion object { @VisibleForTesting