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

Commit 13f31c65 authored by Aditi Katragadda's avatar Aditi Katragadda
Browse files

Display Bluetooth Disconnected Message

The goal is to communicate an error state which indicates that Bluetooth is disconnected. The pop-up that prompts the user to connect to Bluetooth does not appear again on re-entering the app after it has been dismissed. This has been fixed by creating several statuses which
indicate error conditions, download pending or success statuses. The
specific status NO_DEVICE_CONNECTED will allow for the "Bluetooth is
disconnected" message to display by returning a null result.

Tag: #stability
Bug: 275561845
Test: atest AvrcpControllerTest
Change-Id: I6fb279d49db4d6cdbc0850a684a22033390b557e
parent 0d3404e3
Loading
Loading
Loading
Loading
+27 −17
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.bluetooth.BluetoothPrefs;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService.BrowseResult;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.internal.annotations.VisibleForTesting;
@@ -321,10 +322,10 @@ public class AvrcpControllerService extends ProfileService {
     * Get a List of MediaItems that are children of the specified media Id
     *
     * @param parentMediaId The player or folder to get the contents of
     * @return List of Children if available, an empty list if there are none,
     * or null if a search must be performed.
     * @return List of Children if available, an empty list if there are none, or null if a search
     *     must be performed.
     */
    public synchronized List<MediaItem> getContents(String parentMediaId) {
    public synchronized BrowseResult getContents(String parentMediaId) {
        if (DBG) Log.d(TAG, "getContents(" + parentMediaId + ")");

        BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId);
@@ -337,28 +338,37 @@ public class AvrcpControllerService extends ProfileService {
                }
            }
        }

        // If we don't find a node in the tree then do not have any way to browse for the contents.
        // Return an empty list instead.
        if (requestedNode == null) {
            if (DBG) Log.d(TAG, "Didn't find a node");
            return new ArrayList(0);
        } else {
            return new BrowseResult(new ArrayList(0), BrowseResult.ERROR_MEDIA_ID_INVALID);
        }
        if (parentMediaId.equals(BrowseTree.ROOT) && requestedNode.getChildrenCount() == 0) {
            return new BrowseResult(null, BrowseResult.NO_DEVICE_CONNECTED);
        }
        // If we found a node and it belongs to a device then go ahead and make it active
        BluetoothDevice device = requestedNode.getDevice();
        if (device != null) {
            setActiveDevice(device);
        }

        List<MediaItem> contents = requestedNode.getContents();

        if (DBG) Log.d(TAG, "Returning contents");
        if (!requestedNode.isCached()) {
            if (DBG) Log.d(TAG, "node is not cached");
            refreshContents(requestedNode);
            /* Ongoing downloads can have partial results and we want to make sure they get sent
             * to the client. If a download gets kicked off as a result of this request, the
             * contents will be null until the first results arrive.
             */
            return new BrowseResult(contents, BrowseResult.DOWNLOAD_PENDING);
        }
            if (DBG) Log.d(TAG, "Returning contents");
            return requestedNode.getContents();
        }
        return new BrowseResult(contents, BrowseResult.SUCCESS);
    }


    @Override
    protected IProfileServiceBinder initBinder() {
        return new AvrcpControllerServiceBinder(this);
+66 −6
Original line number Diff line number Diff line
@@ -132,11 +132,66 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
        mReceiver = null;
    }

    List<MediaItem> getContents(final String parentMediaId) {
    /**
     * BrowseResult is used to return the contents of a node along with a status. The status is
     * used to indicate success, a pending download, or error conditions. BrowseResult is used in
     * onLoadChildren() and getContents() in BluetoothMediaBrowserService and in getContents() in
     * AvrcpControllerService.
     * The following statuses have been implemented:
     * 1. SUCCESS - Contents have been retrieved successfully.
     * 2. DOWNLOAD_PENDING - Download is in progress and may or may not have contents to return.
     * 3. NO_DEVICE_CONNECTED - If no device is connected there are no contents to be retrieved.
     * 4. ERROR_MEDIA_ID_INVALID - Contents could not be retrieved as the media ID is invalid.
     * 5. ERROR_NO_AVRCP_SERVICE - Contents could not be retrieved as AvrcpControllerService is not
     *                             connected.
     */
    public static class BrowseResult {
        // Possible statuses for onLoadChildren
        public static final byte SUCCESS = 0x00;
        public static final byte DOWNLOAD_PENDING = 0x01;
        public static final byte NO_DEVICE_CONNECTED = 0x02;
        public static final byte ERROR_MEDIA_ID_INVALID = 0x03;
        public static final byte ERROR_NO_AVRCP_SERVICE = 0x04;

        private List<MediaItem> mResults;
        private final byte mStatus;

        List<MediaItem> getResults() {
            return mResults;
        }

        byte getStatus() {
            return mStatus;
        }

        String getStatusString() {
            switch (mStatus) {
                case DOWNLOAD_PENDING:
                    return "DOWNLOAD_PENDING";
                case SUCCESS:
                    return "SUCCESS";
                case NO_DEVICE_CONNECTED:
                    return "NO_DEVICE_CONNECTED";
                case ERROR_MEDIA_ID_INVALID:
                    return "ERROR_MEDIA_ID_INVALID";
                case ERROR_NO_AVRCP_SERVICE:
                    return "ERROR_NO_AVRCP_SERVICE";
                default:
                    return "UNDEFINED_ERROR_CASE";
            }
        }

        BrowseResult(List<MediaItem> results, byte status) {
            mResults = results;
            mStatus = status;
        }
    }

    BrowseResult getContents(final String parentMediaId) {
        AvrcpControllerService avrcpControllerService =
                AvrcpControllerService.getAvrcpControllerService();
        if (avrcpControllerService == null) {
            return new ArrayList(0);
            return new BrowseResult(new ArrayList(0), BrowseResult.ERROR_NO_AVRCP_SERVICE);
        } else {
            return avrcpControllerService.getContents(parentMediaId);
        }
@@ -173,11 +228,16 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
    public synchronized void onLoadChildren(final String parentMediaId,
            final Result<List<MediaItem>> result) {
        if (DBG) Log.d(TAG, "onLoadChildren parentMediaId= " + parentMediaId);
        List<MediaItem> contents = getContents(parentMediaId);
        if (contents == null) {
        BrowseResult contents = getContents(parentMediaId);
        byte status = contents.getStatus();
        if (status == BrowseResult.DOWNLOAD_PENDING && contents == null) {
            Log.i(TAG, "Download pending - no contents, id= " + parentMediaId);
            result.detach();
        } else {
            result.sendResult(contents);
            if (DBG) {
                Log.d(TAG, "id= " + parentMediaId + ", status= " + contents.getStatusString());
            }
            result.sendResult(contents.getResults());
        }
    }

+87 −1
Original line number Diff line number Diff line
@@ -19,9 +19,11 @@ import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -36,6 +38,7 @@ import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService.BrowseResult;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;

@@ -174,7 +177,90 @@ public class AvrcpControllerServiceTest {

        mService.getContents(parentMediaId);

        verify(node).getContents();
        verify(node, atLeastOnce()).getContents();
    }

    /**
     * Pre-conditions: No node in BrowseTree for specified media ID
     * Test: Call AvrcpControllerService.getContents()
     * Expected Output: BrowseResult object with status ERROR_MEDIA_ID_INVALID
     */
    @Test
    public void testGetContentsNoNode_returnInvalidMediaIdStatus() {
        String parentMediaId = "test_parent_media_id";
        when(mStateMachine.findNode(parentMediaId)).thenReturn(null);
        BrowseResult result = mService.getContents(parentMediaId);

        assertThat(result.getStatus()).isEqualTo(BrowseResult.ERROR_MEDIA_ID_INVALID);
    }

    /**
     * Pre-conditions: No device is connected - parent media ID is at the root of the BrowseTree
     * Test: Call AvrcpControllerService.getContents()
     * Expected Output: BrowseResult object with status NO_DEVICE_CONNECTED
     */
    @Test
    public void getContentsNoDeviceConnected_returnNoDeviceConnectedStatus() {
        String parentMediaId = BrowseTree.ROOT;
        BrowseResult result = mService.getContents(parentMediaId);

        assertThat(result.getStatus()).isEqualTo(BrowseResult.NO_DEVICE_CONNECTED);
    }

    /**
     * Pre-conditions: At least one device is connected
     * Test: Call AvrcpControllerService.getContents()
     * Expected Output: BrowseResult object with status SUCCESS
     */
    @Test
    public void getContentsOneDeviceConnected_returnSuccessStatus() {
        String parentMediaId = BrowseTree.ROOT;
        mService.sBrowseTree.onConnected(mRemoteDevice);
        BrowseResult result = mService.getContents(parentMediaId);

        assertThat(result.getStatus()).isEqualTo(BrowseResult.SUCCESS);
    }

    /**
     * Pre-conditions: Node for specified media ID is not cached
     * Test: {@link BrowseTree.BrowseNode#getContents} returns {@code null} when the node has no
     * children/items and the node is not cached.
     * When {@link AvrcpControllerService#getContents} receives a node that is not cached,
     * it should interpret the status as `DOWNLOAD_PENDING`.
     * Expected Output: BrowseResult object with status DOWNLOAD_PENDING; verify that a download
     * request has been sent by checking if mStateMachine.requestContents() is called
     */
    @Test
    public void getContentsNodeNotCached_returnDownloadPendingStatus() {
        String parentMediaId = "test_parent_media_id";
        BrowseTree.BrowseNode node = mock(BrowseTree.BrowseNode.class);
        when(mStateMachine.findNode(parentMediaId)).thenReturn(node);
        when(node.isCached()).thenReturn(false);
        when(node.getDevice()).thenReturn(mRemoteDevice);
        when(node.getID()).thenReturn(parentMediaId);

        BrowseResult result = mService.getContents(parentMediaId);

        verify(mStateMachine, times(1)).requestContents(eq(node));
        assertThat(result.getStatus()).isEqualTo(BrowseResult.DOWNLOAD_PENDING);
    }

    /**
     * Pre-conditions: Parent media ID that is not BrowseTree.ROOT; isCached returns true
     * Test: Call AvrcpControllerService.getContents()
     * Expected Output: BrowseResult object with status SUCCESS
     */
    @Test
    public void getContentsNoErrorConditions_returnsSuccessStatus() {
        String parentMediaId = "test_parent_media_id";
        BrowseTree.BrowseNode node = mock(BrowseTree.BrowseNode.class);
        when(mStateMachine.findNode(parentMediaId)).thenReturn(node);
        when(node.getContents()).thenReturn(new ArrayList(0));
        when(node.isCached()).thenReturn(true);

        BrowseResult result = mService.getContents(parentMediaId);

        assertThat(result.getStatus()).isEqualTo(BrowseResult.SUCCESS);
    }

    @Test