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

Commit 9a29ee90 authored by Etienne Ruffieux's avatar Etienne Ruffieux
Browse files

Prevent updating now playing queue unnecessarily

In our current implementation, we send a "now playing list updated" event each
time the track changes in the queue due to the way we compare the queues.
This patch will eliminate this by comparing the media permanent metadata
instead.

Bug: 346730065
Test: atest BluetoothInstrumentationTests
Flag: Exempt trivial
Change-Id: I3e449d632eb2b92bffd8d939da8f6704400cad40
parent b21ce97e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -101,6 +101,8 @@ public class Metadata implements Cloneable {
                + artist
                + "\" album=\""
                + album
                + "\" genre=\""
                + genre
                + "\" duration="
                + duration
                + " trackPosition="
+34 −1
Original line number Diff line number Diff line
@@ -115,7 +115,7 @@ public class AvrcpTargetService extends ProfileService {

            boolean metadata = !Objects.equals(mCurrentData.metadata, data.metadata);
            boolean state = !MediaPlayerWrapper.playstateEquals(mCurrentData.state, data.state);
            boolean queue = !Objects.equals(mCurrentData.queue, data.queue);
            boolean queue = isQueueUpdated(mCurrentData.queue, data.queue);

            Log.d(
                    TAG,
@@ -561,6 +561,39 @@ public class AvrcpTargetService extends ProfileService {
        mNativeInterface.sendPlayerSettings(repeatMode, shuffleMode);
    }

    /**
     * Compares the {@link Metadata} of the current and new queues
     *
     * <p>Whenever the current playing track changed in the now playing list, its metadata is
     * updated. We should only send an update if the elements of the queue have been modified.
     *
     * <p>Only Title, Album and Artist metadata can be used for comparison. The metadata ID
     * corresponds to the position in the list and is not unique for each media. Genre, duration and
     * cover art are updated when the playing track changes as we are only able to retrieve this
     * information then.
     */
    @VisibleForTesting
    public static boolean isQueueUpdated(List<Metadata> currentQueue, List<Metadata> newQueue) {
        if (newQueue == null && currentQueue == null) {
            return false;
        }
        if (newQueue == null || currentQueue == null || currentQueue.size() != newQueue.size()) {
            return true;
        }

        for (int index = 0; index < currentQueue.size(); index++) {
            Metadata currentMetadata = currentQueue.get(index);
            Metadata newMetadata = newQueue.get(index);

            if (!Objects.equals(currentMetadata.title, newMetadata.title)
                    || !Objects.equals(currentMetadata.artist, newMetadata.artist)
                    || !Objects.equals(currentMetadata.album, newMetadata.album)) {
                return true;
            }
        }
        return false;
    }

    /** Dump debugging information to the string builder */
    public void dump(StringBuilder sb) {
        sb.append("\nProfile: AvrcpTargetService:\n");
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.bluetooth.avrcp;

import static com.google.common.truth.Truth.assertThat;

import android.net.Uri;

import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.audio_util.Image;
import com.android.bluetooth.audio_util.Metadata;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.ArrayList;
import java.util.List;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class AvrcpTargetServiceTest {

    private static final String TEST_DATA = "-1";

    @Test
    public void testQueueUpdateData() {
        List<Metadata> firstQueue = new ArrayList<Metadata>();
        List<Metadata> secondQueue = new ArrayList<Metadata>();

        firstQueue.add(createEmptyMetadata());
        secondQueue.add(createEmptyMetadata());
        assertThat(AvrcpTargetService.isQueueUpdated(firstQueue, secondQueue)).isFalse();

        secondQueue.add(createEmptyMetadata());
        assertThat(AvrcpTargetService.isQueueUpdated(firstQueue, secondQueue)).isTrue();

        firstQueue.add(createEmptyMetadata());
        firstQueue.get(1).genre = TEST_DATA;
        firstQueue.get(1).mediaId = TEST_DATA;
        firstQueue.get(1).trackNum = TEST_DATA;
        firstQueue.get(1).numTracks = TEST_DATA;
        firstQueue.get(1).duration = TEST_DATA;
        firstQueue.get(1).image =
                new Image(
                        InstrumentationRegistry.getInstrumentation().getTargetContext(), Uri.EMPTY);
        assertThat(AvrcpTargetService.isQueueUpdated(firstQueue, secondQueue)).isFalse();

        secondQueue.get(1).title = TEST_DATA;
        assertThat(AvrcpTargetService.isQueueUpdated(firstQueue, secondQueue)).isTrue();

        secondQueue.set(1, createEmptyMetadata());
        secondQueue.get(1).artist = TEST_DATA;
        assertThat(AvrcpTargetService.isQueueUpdated(firstQueue, secondQueue)).isTrue();

        secondQueue.set(1, createEmptyMetadata());
        secondQueue.get(1).album = TEST_DATA;
        assertThat(AvrcpTargetService.isQueueUpdated(firstQueue, secondQueue)).isTrue();
    }

    private Metadata createEmptyMetadata() {
        Metadata.Builder builder = new Metadata.Builder();
        return builder.useDefaults().build();
    }
}