Loading src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseFromQrState.java 0 → 100644 +53 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.settingslib.flags.Flags; class AddSourceWaitForResponseFromQrState extends AddSourceWaitForResponseState { @Nullable private static AddSourceWaitForResponseFromQrState sInstance = null; @VisibleForTesting AddSourceWaitForResponseFromQrState() { } static AddSourceWaitForResponseFromQrState getInstance() { if (sInstance == null) { sInstance = new AddSourceWaitForResponseFromQrState(); } return sInstance; } @Override void onExit(AudioStreamScanHelper scanHelper, AudioStreamsProgressCategoryController.AudioStreamState newState) { if (Flags.audioStreamScanWithFilter()) { scanHelper.restartScanningWithoutFilter(); } } @Override AudioStreamsProgressCategoryController.AudioStreamState getStateEnum() { return ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR; } } src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java +4 −2 Original line number Diff line number Diff line Loading @@ -70,7 +70,7 @@ class AudioStreamStateHandler { + newState); } if (prevStateHandler != null) { prevStateHandler.onExit(scanHelper); prevStateHandler.onExit(scanHelper, newState); } preference.setAudioStreamState(newState); Loading Loading @@ -129,7 +129,9 @@ class AudioStreamStateHandler { /** * Perform action when exiting one state. */ void onExit(AudioStreamScanHelper scanHelper) {} void onExit(AudioStreamScanHelper scanHelper, AudioStreamsProgressCategoryController.AudioStreamState newState) { } /** * The preference summary for the audio stream state (e.g, Scanning...) This method is intended Loading src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java +7 −2 Original line number Diff line number Diff line Loading @@ -132,6 +132,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro SYNCED, // When addSource is called for this source and waiting for response. ADD_SOURCE_WAIT_FOR_RESPONSE, // When addSource is called for this source and waiting for response from a QR code scan. ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR, // When addSource result in a bad code response. ADD_SOURCE_BAD_CODE, // When addSource result in other bad state. Loading Loading @@ -238,7 +240,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro // Find preference by scanned source and decide next state. // Expect one of the following: // 1) No preference existed, create new preference with state SYNCED // 2) WAIT_FOR_SYNC, move to ADD_SOURCE_WAIT_FOR_RESPONSE // 2) WAIT_FOR_SYNC, move to ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR // 3) SOURCE_ADDED, leave as-is @Override public void handleSourceFound(@NonNull BluetoothLeBroadcastMetadata source) { Loading Loading @@ -284,7 +286,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro .setBroadcastCode(mSourceFromQrCode.getBroadcastCode()) .build()); moveToState( existingPreference, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE); existingPreference, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR); } else { // A preference with source founded existed either because it's already // connected (SOURCE_ADDED) or present (SOURCE_PRESENT). Any other reason Loading Loading @@ -760,6 +763,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro case SYNCED -> SyncedState.getInstance(); case WAIT_FOR_SYNC -> WaitForSyncState.getInstance(); case ADD_SOURCE_WAIT_FOR_RESPONSE -> AddSourceWaitForResponseState.getInstance(); case ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR -> AddSourceWaitForResponseFromQrState.getInstance(); case ADD_SOURCE_BAD_CODE -> AddSourceBadCodeState.getInstance(); case ADD_SOURCE_FAILED -> AddSourceFailedState.getInstance(); case SOURCE_PRESENT -> SourcePresentState.getInstance(); Loading src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncState.java +11 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE; import android.app.settings.SettingsEnums; Loading Loading @@ -87,8 +88,16 @@ class WaitForSyncState extends AudioStreamStateHandler { } @Override void onExit(AudioStreamScanHelper scanHelper) { if (Flags.audioStreamScanWithFilter()) { void onExit(AudioStreamScanHelper scanHelper, AudioStreamsProgressCategoryController.AudioStreamState newState) { // If the new state indicates that we're in the process of adding source // (ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR), we will not stop and restart scanning here, // instead, we will wait until the add source operation's completion. This is to preserve // the sync link which is needed for the add source operation, as bluetooth stack clears // active syncs when stopping scanning. If we're moving to other states, we can safely stop // and restart scanning. if (Flags.audioStreamScanWithFilter() && !newState.equals( ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR)) { scanHelper.restartScanningWithoutFilter(); } } Loading tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseFromQrStateTest.java 0 → 100644 +81 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.Context; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.core.app.ApplicationProvider; import com.android.settingslib.flags.Flags; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class AddSourceWaitForResponseFromQrStateTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private final Context mContext = spy(ApplicationProvider.getApplicationContext()); @Mock private AudioStreamScanHelper mScanHelper; private AddSourceWaitForResponseFromQrState mInstance; @Before public void setUp() { mInstance = new AddSourceWaitForResponseFromQrState(); } @Test public void testGetInstance() { mInstance = AddSourceWaitForResponseFromQrState.getInstance(); assertThat(mInstance).isNotNull(); assertThat(mInstance).isInstanceOf(AudioStreamStateHandler.class); } @Test public void testGetStateEnum() { AudioStreamsProgressCategoryController.AudioStreamState stateEnum = mInstance.getStateEnum(); assertThat(stateEnum) .isEqualTo( AudioStreamsProgressCategoryController.AudioStreamState .ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR); } @Test public void testOnExit_restartScanWithoutFilter() { mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_SCAN_WITH_FILTER); mInstance.onExit(mScanHelper, AudioStreamsProgressCategoryController.AudioStreamState.SYNCED); verify(mScanHelper).restartScanningWithoutFilter(); } } Loading
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseFromQrState.java 0 → 100644 +53 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.settingslib.flags.Flags; class AddSourceWaitForResponseFromQrState extends AddSourceWaitForResponseState { @Nullable private static AddSourceWaitForResponseFromQrState sInstance = null; @VisibleForTesting AddSourceWaitForResponseFromQrState() { } static AddSourceWaitForResponseFromQrState getInstance() { if (sInstance == null) { sInstance = new AddSourceWaitForResponseFromQrState(); } return sInstance; } @Override void onExit(AudioStreamScanHelper scanHelper, AudioStreamsProgressCategoryController.AudioStreamState newState) { if (Flags.audioStreamScanWithFilter()) { scanHelper.restartScanningWithoutFilter(); } } @Override AudioStreamsProgressCategoryController.AudioStreamState getStateEnum() { return ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR; } }
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java +4 −2 Original line number Diff line number Diff line Loading @@ -70,7 +70,7 @@ class AudioStreamStateHandler { + newState); } if (prevStateHandler != null) { prevStateHandler.onExit(scanHelper); prevStateHandler.onExit(scanHelper, newState); } preference.setAudioStreamState(newState); Loading Loading @@ -129,7 +129,9 @@ class AudioStreamStateHandler { /** * Perform action when exiting one state. */ void onExit(AudioStreamScanHelper scanHelper) {} void onExit(AudioStreamScanHelper scanHelper, AudioStreamsProgressCategoryController.AudioStreamState newState) { } /** * The preference summary for the audio stream state (e.g, Scanning...) This method is intended Loading
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java +7 −2 Original line number Diff line number Diff line Loading @@ -132,6 +132,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro SYNCED, // When addSource is called for this source and waiting for response. ADD_SOURCE_WAIT_FOR_RESPONSE, // When addSource is called for this source and waiting for response from a QR code scan. ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR, // When addSource result in a bad code response. ADD_SOURCE_BAD_CODE, // When addSource result in other bad state. Loading Loading @@ -238,7 +240,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro // Find preference by scanned source and decide next state. // Expect one of the following: // 1) No preference existed, create new preference with state SYNCED // 2) WAIT_FOR_SYNC, move to ADD_SOURCE_WAIT_FOR_RESPONSE // 2) WAIT_FOR_SYNC, move to ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR // 3) SOURCE_ADDED, leave as-is @Override public void handleSourceFound(@NonNull BluetoothLeBroadcastMetadata source) { Loading Loading @@ -284,7 +286,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro .setBroadcastCode(mSourceFromQrCode.getBroadcastCode()) .build()); moveToState( existingPreference, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE); existingPreference, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR); } else { // A preference with source founded existed either because it's already // connected (SOURCE_ADDED) or present (SOURCE_PRESENT). Any other reason Loading Loading @@ -760,6 +763,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro case SYNCED -> SyncedState.getInstance(); case WAIT_FOR_SYNC -> WaitForSyncState.getInstance(); case ADD_SOURCE_WAIT_FOR_RESPONSE -> AddSourceWaitForResponseState.getInstance(); case ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR -> AddSourceWaitForResponseFromQrState.getInstance(); case ADD_SOURCE_BAD_CODE -> AddSourceBadCodeState.getInstance(); case ADD_SOURCE_FAILED -> AddSourceFailedState.getInstance(); case SOURCE_PRESENT -> SourcePresentState.getInstance(); Loading
src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncState.java +11 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE; import android.app.settings.SettingsEnums; Loading Loading @@ -87,8 +88,16 @@ class WaitForSyncState extends AudioStreamStateHandler { } @Override void onExit(AudioStreamScanHelper scanHelper) { if (Flags.audioStreamScanWithFilter()) { void onExit(AudioStreamScanHelper scanHelper, AudioStreamsProgressCategoryController.AudioStreamState newState) { // If the new state indicates that we're in the process of adding source // (ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR), we will not stop and restart scanning here, // instead, we will wait until the add source operation's completion. This is to preserve // the sync link which is needed for the add source operation, as bluetooth stack clears // active syncs when stopping scanning. If we're moving to other states, we can safely stop // and restart scanning. if (Flags.audioStreamScanWithFilter() && !newState.equals( ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR)) { scanHelper.restartScanningWithoutFilter(); } } Loading
tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseFromQrStateTest.java 0 → 100644 +81 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.Context; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.core.app.ApplicationProvider; import com.android.settingslib.flags.Flags; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class AddSourceWaitForResponseFromQrStateTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private final Context mContext = spy(ApplicationProvider.getApplicationContext()); @Mock private AudioStreamScanHelper mScanHelper; private AddSourceWaitForResponseFromQrState mInstance; @Before public void setUp() { mInstance = new AddSourceWaitForResponseFromQrState(); } @Test public void testGetInstance() { mInstance = AddSourceWaitForResponseFromQrState.getInstance(); assertThat(mInstance).isNotNull(); assertThat(mInstance).isInstanceOf(AudioStreamStateHandler.class); } @Test public void testGetStateEnum() { AudioStreamsProgressCategoryController.AudioStreamState stateEnum = mInstance.getStateEnum(); assertThat(stateEnum) .isEqualTo( AudioStreamsProgressCategoryController.AudioStreamState .ADD_SOURCE_WAIT_FOR_RESPONSE_FROM_QR); } @Test public void testOnExit_restartScanWithoutFilter() { mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_SCAN_WITH_FILTER); mInstance.onExit(mScanHelper, AudioStreamsProgressCategoryController.AudioStreamState.SYNCED); verify(mScanHelper).restartScanningWithoutFilter(); } }