Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 04d37598 authored by chelseahao's avatar chelseahao Committed by Chelsea Hao
Browse files

To preserve active sync, restart scan after add source completion.

Flag: com.android.settingslib.flags.enable_le_audio_sharing
Test: atest
Bug: 418302765
Change-Id: I26fca486aeb3ed2d8091008622de82fc0a182917
parent 429b5ba5
Loading
Loading
Loading
Loading
+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;
    }
}
+4 −2
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ class AudioStreamStateHandler {
                            + newState);
        }
        if (prevStateHandler != null) {
            prevStateHandler.onExit(scanHelper);
            prevStateHandler.onExit(scanHelper, newState);
        }

        preference.setAudioStreamState(newState);
@@ -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
+7 −2
Original line number Diff line number Diff line
@@ -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.
@@ -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) {
@@ -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
@@ -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();
+11 −2
Original line number Diff line number Diff line
@@ -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;
@@ -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();
        }
    }
+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