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

Commit 1b669b6e authored by Sal Savage's avatar Sal Savage
Browse files

Have AvrcpControllerService own the active device

This code moves the active device management from functions around a
static objects on the state machine to a functions from the
AvrcpControllerService.

AvrcpControllerService now orchestrates the active device. A device will
become active when:
1. It connects and no other device is connected
2. When you browse that device
3. When you play an item from that device

AvrcpControllerService notifies state machines of their active state
with a new message that goes on the handler. State machines then update
the BluetoothMediaBrowserService accordingly. When there is no active
device set, AvrcpControllerService will clear the
BluetoothMediaBrowserService status. When a new/different device is set,
AvrcpControllerService will send a pause to the previous device.

New tests have been added to prove state machine behavior.

Tag: #stability
Bug: 180416295
Bug: 180416562
Bug: 181374758
Bug: 181374973
Test: atest BluetoothInstrumentationTests
Change-Id: I251498bec049fc4cdfd4e657558649c5cfd3dd6c
parent 8496b3a4
Loading
Loading
Loading
Loading
+86 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.util.Log;

import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.btservice.ProfileService;

import java.util.ArrayList;
@@ -88,12 +89,18 @@ public class AvrcpControllerService extends ProfileService {
    public static final int KEY_STATE_PRESSED = 0;
    public static final int KEY_STATE_RELEASED = 1;

    /* Active Device State Variables */
    public static final int DEVICE_STATE_INACTIVE = 0;
    public static final int DEVICE_STATE_ACTIVE = 1;

    static BrowseTree sBrowseTree;
    private static AvrcpControllerService sService;
    private final BluetoothAdapter mAdapter;

    protected Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap =
            new ConcurrentHashMap<>(1);
    private BluetoothDevice mActiveDevice = null;
    private final Object mActiveDeviceLock = new Object();

    private boolean mCoverArtEnabled = false;
    protected AvrcpCoverArtManager mCoverArtManager;
@@ -139,11 +146,13 @@ public class AvrcpControllerService extends ProfileService {
        // Start the media browser service.
        Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
        startService(startIntent);
        setActiveDevice(null);
        return true;
    }

    @Override
    protected synchronized boolean stop() {
        setActiveDevice(null);
        Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
        stopService(stopIntent);
        for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
@@ -163,6 +172,56 @@ public class AvrcpControllerService extends ProfileService {
        return sService;
    }

    /**
     * Get the current active device
     */
    public BluetoothDevice getActiveDevice() {
        synchronized (mActiveDeviceLock) {
            return mActiveDevice;
        }
    }

    /**
     * Set the current active device, notify devices of activity status
     */
    private boolean setActiveDevice(BluetoothDevice device) {
        A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
        if (a2dpSinkService == null) {
            return false;
        }

        BluetoothDevice currentActiveDevice = getActiveDevice();
        if ((device == null && currentActiveDevice == null)
                || (device != null && device.equals(currentActiveDevice))) {
            return true;
        }

        // Try and update the active device
        synchronized (mActiveDeviceLock) {
            if (a2dpSinkService.setActiveDevice(device)) {
                mActiveDevice = device;

                // Pause the old active device
                if (currentActiveDevice != null) {
                    AvrcpControllerStateMachine oldStateMachine =
                            getStateMachine(currentActiveDevice);
                    if (oldStateMachine != null) {
                        oldStateMachine.setDeviceState(DEVICE_STATE_INACTIVE);
                    }
                }

                AvrcpControllerStateMachine stateMachine = getStateMachine(device);
                if (stateMachine != null) {
                    stateMachine.setDeviceState(DEVICE_STATE_ACTIVE);
                } else {
                    BluetoothMediaBrowserService.reset();
                }
                return true;
            }
        }
        return false;
    }

    protected AvrcpControllerStateMachine newStateMachine(BluetoothDevice device) {
        return new AvrcpControllerStateMachine(device, this);
    }
@@ -198,6 +257,10 @@ public class AvrcpControllerService extends ProfileService {
                requestedNode = stateMachine.findNode(parentMediaId);
                if (requestedNode != null) {
                    if (DBG) Log.d(TAG, "Found a node");
                    BluetoothDevice device = stateMachine.getDevice();
                    if (device != null) {
                        setActiveDevice(device);
                    }
                    stateMachine.playItem(requestedNode);
                    break;
                }
@@ -234,6 +297,12 @@ public class AvrcpControllerService extends ProfileService {
            if (DBG) Log.d(TAG, "Didn't find a node");
            return new ArrayList(0);
        } else {
            // 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);
            }

            if (!requestedNode.isCached()) {
                if (DBG) Log.d(TAG, "node is not cached");
                refreshContents(requestedNode);
@@ -355,8 +424,15 @@ public class AvrcpControllerService extends ProfileService {
        AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
        if (remoteControlConnected || browsingConnected) {
            stateMachine.connect(event);
            // The first device to connect gets to be the active device
            if (getActiveDevice() == null) {
                setActiveDevice(device);
            }
        } else {
            stateMachine.disconnect();
            if (device.equals(getActiveDevice())) {
                setActiveDevice(null);
            }
        }
    }

@@ -729,6 +805,10 @@ public class AvrcpControllerService extends ProfileService {
     * Remove state machine from device map once it is no longer needed.
     */
    public void removeStateMachine(AvrcpControllerStateMachine stateMachine) {
        BluetoothDevice device = stateMachine.getDevice();
        if (device.equals(getActiveDevice())) {
            setActiveDevice(null);
        }
        mDeviceStateMap.remove(stateMachine.getDevice());
    }

@@ -737,6 +817,9 @@ public class AvrcpControllerService extends ProfileService {
    }

    protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) {
        if (device == null) {
            return null;
        }
        return mDeviceStateMap.get(device);
    }

@@ -783,6 +866,7 @@ public class AvrcpControllerService extends ProfileService {
    public void dump(StringBuilder sb) {
        super.dump(sb);
        ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
        ProfileService.println(sb, "Active Device = " + mActiveDevice);

        for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
            ProfileService.println(sb,
@@ -795,6 +879,8 @@ public class AvrcpControllerService extends ProfileService {
        if (mCoverArtManager != null) {
            sb.append("\n  " + mCoverArtManager.toString());
        }

        sb.append("\n  " + BluetoothMediaBrowserService.dump() + "\n");
    }

    /*JNI*/
+30 −52
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ class AvrcpControllerStateMachine extends StateMachine {
    //0->99 Events from Outside
    public static final int CONNECT = 1;
    public static final int DISCONNECT = 2;
    public static final int ACTIVE_DEVICE_CHANGE = 3;

    //100->199 Internal Events
    protected static final int CLEANUP = 100;
@@ -184,7 +185,7 @@ class AvrcpControllerStateMachine extends StateMachine {
     *
     * @return device in focus
     */
    public synchronized BluetoothDevice getDevice() {
    public BluetoothDevice getDevice() {
        return mDevice;
    }

@@ -223,55 +224,20 @@ class AvrcpControllerStateMachine extends StateMachine {
        ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
                + mDevice.getName() + ") " + this.toString());
        ProfileService.println(sb, "isActive: " + isActive());
        ProfileService.println(sb, "Control: " + mRemoteControlConnected);
        ProfileService.println(sb, "Browsing: " + mBrowsingConnected);
    }

    @VisibleForTesting
    boolean isActive() {
        return mDevice == sActiveDevice;
    }

    /*
     * requestActive
     *
     * Set the current device active if nothing an already connected device isn't playing
     */
    private boolean requestActive() {
        if (sActiveDevice == null
                || BluetoothMediaBrowserService.getPlaybackState()
                != PlaybackStateCompat.STATE_PLAYING) {
            return setActive(true);
        }
        return false;
        return mDevice.equals(mService.getActiveDevice());
    }

    /**
     * Attempt to set the active status for this device
     */
    boolean setActive(boolean becomeActive) {
        logD("setActive(" + becomeActive + ")");
        A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
        if (a2dpSinkService == null) {
            return false;
        }
        if (becomeActive) {
            if (isActive()) {
                return true;
            }

            if (a2dpSinkService.setActiveDevice(mDevice)) {
                sActiveDevice = mDevice;
                BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
                BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
                BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
            }
            return mDevice == sActiveDevice;
        } else if (isActive()) {
            sActiveDevice = null;
            a2dpSinkService.setActiveDevice(null);
            BluetoothMediaBrowserService.trackChanged(null);
            BluetoothMediaBrowserService.addressedPlayerChanged(null);
        }
        return true;
    public void setDeviceState(int state) {
        sendMessage(ACTIVE_DEVICE_CHANGE, state);
    }

    @Override
@@ -286,10 +252,6 @@ class AvrcpControllerStateMachine extends StateMachine {
    }

    synchronized void onBrowsingConnected() {
        if (mBrowsingConnected) return;
        mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
        BluetoothMediaBrowserService.notifyChanged(mService
                .sBrowseTree.mRootNode);
        mBrowsingConnected = true;
    }

@@ -303,10 +265,6 @@ class AvrcpControllerStateMachine extends StateMachine {
        if (isActive()) {
            BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
        }
        mService.sBrowseTree.mRootNode.removeChild(
                mBrowseTree.mRootNode);
        BluetoothMediaBrowserService.notifyChanged(mService
                .sBrowseTree.mRootNode);
        removeUnusedArtwork(previousTrackUuid);
        removeUnusedArtworkFromBrowseTree();
        mBrowsingConnected = false;
@@ -426,6 +384,10 @@ class AvrcpControllerStateMachine extends StateMachine {
                case CLEANUP:
                    mService.removeStateMachine(AvrcpControllerStateMachine.this);
                    break;
                case ACTIVE_DEVICE_CHANGE:
                    // Wait until we're connected to process this
                    deferMessage(message);
                    break;
            }
            return true;
        }
@@ -448,8 +410,9 @@ class AvrcpControllerStateMachine extends StateMachine {
        @Override
        public void enter() {
            if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
                requestActive();
                broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
                mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
                BluetoothMediaBrowserService.notifyChanged(mService.sBrowseTree.mRootNode);
                connectCoverArt(); // only works if we have a valid PSM
            } else {
                logD("ReEnteringConnected");
@@ -461,6 +424,21 @@ class AvrcpControllerStateMachine extends StateMachine {
        public boolean processMessage(Message msg) {
            logD(STATE_TAG + " processMessage " + msg.what);
            switch (msg.what) {
                case ACTIVE_DEVICE_CHANGE:
                    int state = msg.arg1;
                    if (state == AvrcpControllerService.DEVICE_STATE_ACTIVE) {
                        BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
                        BluetoothMediaBrowserService.trackChanged(
                                mAddressedPlayer.getCurrentTrack());
                        BluetoothMediaBrowserService.notifyChanged(
                                mAddressedPlayer.getPlaybackState());
                        BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
                    } else {
                        sendMessage(MSG_AVRCP_PASSTHRU,
                                AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
                    }
                    return true;

                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
                    mVolumeChangedNotificationsToIgnore++;
                    removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
@@ -643,7 +621,6 @@ class AvrcpControllerStateMachine extends StateMachine {
        }

        private void processPlayItem(BrowseTree.BrowseNode node) {
            setActive(true);
            if (node == null) {
                Log.w(TAG, "Invalid item to play");
            } else {
@@ -970,7 +947,8 @@ class AvrcpControllerStateMachine extends StateMachine {
        public void enter() {
            disconnectCoverArt();
            onBrowsingDisconnected();
            setActive(false);
            mService.sBrowseTree.mRootNode.removeChild(mBrowseTree.mRootNode);
            BluetoothMediaBrowserService.notifyChanged(mService.sBrowseTree.mRootNode);
            broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
            transitionTo(mDisconnected);
        }
+62 −3
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat.MediaItem;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
@@ -154,19 +155,21 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
    private void updateNowPlayingQueue(BrowseTree.BrowseNode node) {
        List<MediaItem> songList = node.getContents();
        mMediaQueue.clear();
        if (songList != null) {
        if (songList != null && songList.size() > 0) {
            for (MediaItem song : songList) {
                mMediaQueue.add(new MediaSessionCompat.QueueItem(
                        song.getDescription(),
                        mMediaQueue.size()));
            }
        }
            mSession.setQueue(mMediaQueue);
        } else {
            mSession.setQueue(null);
        }
    }

    private void clearNowPlayingQueue() {
        mMediaQueue.clear();
        mSession.setQueue(mMediaQueue);
        mSession.setQueue(null);
    }

    static synchronized void notifyChanged(BrowseTree.BrowseNode node) {
@@ -284,4 +287,60 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
            return null;
        }
    }

    /**
     * Reset the state of BluetoothMediaBrowserService to that before a device connected
     */
    public static synchronized void reset() {
        if (sBluetoothMediaBrowserService != null) {
            sBluetoothMediaBrowserService.clearNowPlayingQueue();
            sBluetoothMediaBrowserService.mSession.setMetadata(null);
            sBluetoothMediaBrowserService.setErrorPlaybackState();
            sBluetoothMediaBrowserService.mSession.setCallback(null);
            if (DBG) Log.d(TAG, "Service state has been reset");
        } else {
            Log.w(TAG, "reset unavailable");
        }
    }

    /**
     * Get the state of the BluetoothMediaBrowserService as a debug string
     */
    public static synchronized String dump() {
        StringBuilder sb = new StringBuilder();
        sb.append(TAG + ":");
        if (sBluetoothMediaBrowserService != null) {
            MediaSessionCompat session = sBluetoothMediaBrowserService.getSession();
            MediaControllerCompat controller = session.getController();
            MediaMetadataCompat metadata = controller == null ? null : controller.getMetadata();
            PlaybackStateCompat playbackState =
                    controller == null ? null : controller.getPlaybackState();
            List<MediaSessionCompat.QueueItem> queue =
                    controller == null ? null : controller.getQueue();
            if (metadata != null) {
                sb.append("\n    track={");
                sb.append("title=" + metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE));
                sb.append(", artist="
                        + metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST));
                sb.append(", album=" + metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM));
                sb.append(", track_number="
                        + metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER));
                sb.append(", total_tracks="
                        + metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS));
                sb.append(", genre=" + metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE));
                sb.append(", album_art="
                        + metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI));
                sb.append("}");
            } else {
                sb.append("\n    track=" + metadata);
            }
            sb.append("\n    playbackState=" + playbackState);
            sb.append("\n    queue=" + queue);
            sb.append("\n    internal_queue=" + sBluetoothMediaBrowserService.mMediaQueue);
        } else {
            Log.w(TAG, "dump Unavailable");
            sb.append(" null");
        }
        return sb.toString();
    }
}
+381 −186

File changed.

Preview size limit exceeded, changes collapsed.