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

Commit 75858425 authored by Sal Savage's avatar Sal Savage Committed by Automerger Merge Worker
Browse files

Use mapped UUIDs to identify handles and invalidate/delete on disconnect am: f86238ca

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Bluetooth/+/11922316

Change-Id: Ief336ef5683d87b248f390473e92c594f71370c0
parents f425c419 f86238ca
Loading
Loading
Loading
Loading
+15 −3
Original line number Diff line number Diff line
@@ -103,13 +103,13 @@ public class AvrcpControllerService extends ProfileService {
        public void onImageDownloadComplete(BluetoothDevice device,
                AvrcpCoverArtManager.DownloadEvent event) {
            if (DBG) {
                Log.d(TAG, "Image downloaded [device: " + device + ", handle: " + event.getHandle()
                Log.d(TAG, "Image downloaded [device: " + device + ", uuid: " + event.getUuid()
                        + ", uri: " + event.getUri());
            }
            AvrcpControllerStateMachine stateMachine = getStateMachine(device);
            if (stateMachine == null) {
                Log.e(TAG, "No state machine found for device " + device);
                mCoverArtManager.removeImage(device, event.getHandle());
                mCoverArtManager.removeImage(device, event.getUuid());
                return;
            }
            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_IMAGE_DOWNLOADED,
@@ -423,6 +423,12 @@ public class AvrcpControllerService extends ProfileService {
            aib.setItemType(AvrcpItem.TYPE_MEDIA);
            aib.setUuid(UUID.randomUUID().toString());
            AvrcpItem item = aib.build();
            if (mCoverArtManager != null) {
                String handle = item.getCoverArtHandle();
                if (handle != null) {
                    item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle));
                }
            }
            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED,
                    item);
        }
@@ -529,13 +535,19 @@ public class AvrcpControllerService extends ProfileService {
                    + items.length + " items.");
        }

        BluetoothDevice device = mAdapter.getRemoteDevice(address);
        List<AvrcpItem> itemsList = new ArrayList<>();
        for (AvrcpItem item : items) {
            if (VDBG) Log.d(TAG, item.toString());
            if (mCoverArtManager != null) {
                String handle = item.getCoverArtHandle();
                if (handle != null) {
                    item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle));
                }
            }
            itemsList.add(item);
        }

        BluetoothDevice device = mAdapter.getRemoteDevice(address);
        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
        if (stateMachine != null) {
            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS,
+12 −10
Original line number Diff line number Diff line
@@ -560,13 +560,13 @@ class AvrcpControllerStateMachine extends StateMachine {
                case MESSAGE_PROCESS_IMAGE_DOWNLOADED:
                    AvrcpCoverArtManager.DownloadEvent event =
                            (AvrcpCoverArtManager.DownloadEvent) msg.obj;
                    String handle = event.getHandle();
                    String uuid = event.getUuid();
                    Uri uri = event.getUri();
                    logD("Received image for " + handle + " at " + uri.toString());
                    logD("Received image for " + uuid + " at " + uri.toString());

                    // Let the addressed player know we got an image so it can see if the current
                    // track now has cover artwork
                    boolean addedArtwork = mAddressedPlayer.notifyImageDownload(handle, uri);
                    boolean addedArtwork = mAddressedPlayer.notifyImageDownload(uuid, uri);
                    if (addedArtwork && isActive()) {
                        BluetoothMediaBrowserService.trackChanged(
                                mAddressedPlayer.getCurrentTrack());
@@ -574,7 +574,7 @@ class AvrcpControllerStateMachine extends StateMachine {

                    // Let the browse tree know of the newly downloaded image so it can attach it to
                    // all the items that need it. Notify of changed nodes accordingly
                    Set<BrowseTree.BrowseNode> nodes = mBrowseTree.notifyImageDownload(handle, uri);
                    Set<BrowseTree.BrowseNode> nodes = mBrowseTree.notifyImageDownload(uuid, uri);
                    for (BrowseTree.BrowseNode node : nodes) {
                        notifyChanged(node);
                    }
@@ -705,9 +705,11 @@ class AvrcpControllerStateMachine extends StateMachine {

                    // Queue up image download if the item has an image and we don't have it yet
                    // Only do this if the feature is enabled.
                    if (shouldDownloadBrowsedImages()) {
                    for (AvrcpItem track : folderList) {
                        if (shouldDownloadBrowsedImages()) {
                            downloadImageIfNeeded(track);
                        } else {
                            track.setCoverArtUuid(null);
                        }
                    }

@@ -984,14 +986,14 @@ class AvrcpControllerStateMachine extends StateMachine {

    private void downloadImageIfNeeded(AvrcpItem track) {
        if (mCoverArtManager == null) return;
        String handle = track.getCoverArtHandle();
        String uuid = track.getCoverArtUuid();
        Uri imageUri = null;
        if (handle != null) {
            imageUri = mCoverArtManager.getImageUri(mDevice, handle);
        if (uuid != null) {
            imageUri = mCoverArtManager.getImageUri(mDevice, uuid);
            if (imageUri != null) {
                track.setCoverArtLocation(imageUri);
            } else {
                mCoverArtManager.downloadImage(mDevice, handle);
                mCoverArtManager.downloadImage(mDevice, uuid);
            }
        }
    }
+133 −25
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import android.os.SystemProperties;
import android.util.Log;

import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.obex.ResponseCodes;
@@ -46,6 +48,7 @@ public class AvrcpCoverArtManager {

    private final AvrcpControllerService mService;
    protected final Map<BluetoothDevice, AvrcpBipClient> mClients = new ConcurrentHashMap<>(1);
    private Map<BluetoothDevice, AvrcpBipSession> mBipSessions = new ConcurrentHashMap<>(1);
    private final AvrcpCoverArtStorage mCoverArtStorage;
    private final Callback mCallback;
    private final String mDownloadScheme;
@@ -55,14 +58,14 @@ public class AvrcpCoverArtManager {
     * retrieve the image from storage.
     */
    public class DownloadEvent {
        final String mImageHandle;
        final String mImageUuid;
        final Uri mUri;
        public DownloadEvent(String handle, Uri uri) {
            mImageHandle = handle;
        public DownloadEvent(String uuid, Uri uri) {
            mImageUuid = uuid;
            mUri = uri;
        }
        public String getHandle() {
            return mImageHandle;
        public String getUuid() {
            return mImageUuid;
        }
        public Uri getUri() {
            return mUri;
@@ -80,6 +83,44 @@ public class AvrcpCoverArtManager {
        void onImageDownloadComplete(BluetoothDevice device, DownloadEvent event);
    }

    /**
     * A thread-safe collection of BIP connection specific imformation meant to be cleared each
     * time a client disconnects from the Target's BIP OBEX server.
     *
     * Currently contains the mapping of image handles seen to assigned UUIDs.
     */
    private class AvrcpBipSession {
        private final BluetoothDevice mDevice;
        private Map<String, String> mUuids = new ConcurrentHashMap<>(1); /* handle -> UUID */
        private Map<String, String> mHandles = new ConcurrentHashMap<>(1); /* UUID -> handle */

        AvrcpBipSession(BluetoothDevice device) {
            mDevice = device;
        }

        public String getHandleUuid(String handle) {
            if (handle == null) return null;
            String newUuid = UUID.randomUUID().toString();
            String existingUuid = mUuids.putIfAbsent(handle, newUuid);
            if (existingUuid != null) return existingUuid;
            mHandles.put(newUuid, handle);
            return newUuid;
        }

        public String getUuidHandle(String uuid) {
            return mHandles.get(uuid);
        }

        public void clearHandleUuids() {
            mUuids.clear();
            mHandles.clear();
        }

        public Set<String> getSessionHandles() {
            return mUuids.keySet();
        }
    }

    public AvrcpCoverArtManager(AvrcpControllerService service, Callback callback) {
        mService = service;
        mCoverArtStorage = new AvrcpCoverArtStorage(mService);
@@ -101,6 +142,7 @@ public class AvrcpCoverArtManager {
        if (mClients.containsKey(device)) return false;
        AvrcpBipClient client = new AvrcpBipClient(device, psm, new BipClientCallback(device));
        mClients.put(device, client);
        mBipSessions.put(device, new AvrcpBipSession(device));
        return true;
    }

@@ -136,6 +178,7 @@ public class AvrcpCoverArtManager {
        }
        client.shutdown();
        mClients.remove(device);
        mBipSessions.remove(device);
        mCoverArtStorage.removeImagesForDevice(device);
        return true;
    }
@@ -165,16 +208,59 @@ public class AvrcpCoverArtManager {
        return client.getState();
    }

     /**
     * Get the UUID for an image handle coming from a particular device.
     *
     * This UUID is used to request and track downloads.
     *
     * Image handles are only good for the life of the BIP client. Since this connection is torn
     * down frequently by specification, we have a layer of indirection to the images in the form
     * of an UUID. This UUID will allow images to be identified outside the connection lifecycle.
     * It also allows handles to be reused by the target in ways that won't impact image consumer's
     * cache schemes.
     *
     * @param device The Bluetooth device you want a handle from
     * @param handle The image handle you want a UUID for
     * @return A string UUID by which the handle can be identified during the life of the BIP
     *         connection.
     */
    public String getUuidForHandle(BluetoothDevice device, String handle) {
        AvrcpBipSession session = getSession(device);
        if (session == null || handle == null) return null;
        return session.getHandleUuid(handle);
    }

    /**
     * Get the handle thats associated with a particular UUID.
     *
     * The handle must have been seen during this connection.
     *
     * @param device The Bluetooth device you want a handle from
     * @param uuid The UUID you want the associated handle for
     * @return The image handle associated with this UUID if it exists, null otherwise.
     */
    public String getHandleForUuid(BluetoothDevice device, String uuid) {
        AvrcpBipSession session = getSession(device);
        if (session == null || uuid == null) return null;
        return session.getUuidHandle(uuid);
    }

    private void clearHandleUuids(BluetoothDevice device) {
        AvrcpBipSession session = getSession(device);
        if (session == null) return;
        session.clearHandleUuids();
    }

    /**
     * Get the Uri of an image if it has already been downloaded.
     *
     * @param device The remote Bluetooth device you wish to get an image for
     * @param imageHandle The handle associated with the image you want
     * @param imageUuid The UUID associated with the image you want
     * @return A Uri the image can be found at, null if it does not exist
     */
    public Uri getImageUri(BluetoothDevice device, String imageHandle) {
        if (mCoverArtStorage.doesImageExist(device, imageHandle)) {
            return AvrcpCoverArtProvider.getImageUri(device, imageHandle);
    public Uri getImageUri(BluetoothDevice device, String imageUuid) {
        if (mCoverArtStorage.doesImageExist(device, imageUuid)) {
            return AvrcpCoverArtProvider.getImageUri(device, imageUuid);
        }
        return null;
    }
@@ -190,11 +276,12 @@ public class AvrcpCoverArtManager {
     * Getting image properties and the image are both asynchronous in nature.
     *
     * @param device The remote Bluetooth device you wish to download from
     * @param imageHandle The handle associated with the image you wish to download
     * @param imageUuid The UUID associated with the image you wish to download. This will be
     *                  translated into an image handle.
     * @return A Uri that will be assign to the image once the download is complete
     */
    public Uri downloadImage(BluetoothDevice device, String imageHandle) {
        debug("Download Image - device: " + device.getAddress() + ", Handle: " + imageHandle);
    public Uri downloadImage(BluetoothDevice device, String imageUuid) {
        debug("Download Image - device: " + device.getAddress() + ", Handle: " + imageUuid);
        AvrcpBipClient client = getClient(device);
        if (client == null) {
            error("Cannot download an image. No client is available.");
@@ -202,19 +289,24 @@ public class AvrcpCoverArtManager {
        }

        // Check to see if we have the image already. No need to download it if we do have it.
        if (mCoverArtStorage.doesImageExist(device, imageHandle)) {
        if (mCoverArtStorage.doesImageExist(device, imageUuid)) {
            debug("Image is already downloaded");
            return AvrcpCoverArtProvider.getImageUri(device, imageHandle);
            return AvrcpCoverArtProvider.getImageUri(device, imageUuid);
        }

        // Getting image properties will return via the callback created when connecting, which
        // invokes the download image function after we're returned the properties. If we already
        // have the image, GetImageProperties returns true but does not start a download.
        String imageHandle = getHandleForUuid(device, imageUuid);
        if (imageHandle == null) {
            warn("No handle for UUID");
            return null;
        }
        boolean status = client.getImageProperties(imageHandle);
        if (!status) return null;

        // Return the Uri that the caller should use to retrieve the image
        return AvrcpCoverArtProvider.getImageUri(device, imageHandle);
        return AvrcpCoverArtProvider.getImageUri(device, imageUuid);
    }

    /**
@@ -223,8 +315,8 @@ public class AvrcpCoverArtManager {
     * @param device The remote Bluetooth device associated with the image
     * @param imageHandle The handle associated with the image you wish to remove
     */
    public void removeImage(BluetoothDevice device, String imageHandle) {
        mCoverArtStorage.removeImage(device, imageHandle);
    public void removeImage(BluetoothDevice device, String imageUuid) {
        mCoverArtStorage.removeImage(device, imageUuid);
    }

    /**
@@ -237,6 +329,16 @@ public class AvrcpCoverArtManager {
        return mClients.get(device);
    }

    /**
     * Get a device's BIP session information, if it exists
     *
     * @param device The device you want the client for
     * @return The AvrcpBipSession object associated with the device, or null if it doesn't exist
     */
    private AvrcpBipSession getSession(BluetoothDevice device) {
        return mBipSessions.get(device);
    }

    /**
     * Determines our preferred download descriptor from the list of available image download
     * formats presented in the image properties object.
@@ -277,9 +379,8 @@ public class AvrcpCoverArtManager {
        public void onConnectionStateChanged(int oldState, int newState) {
            debug(mDevice.getAddress() + ": " + oldState + " -> " + newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                // The spec says handles are only good for the life an an OBEX connection. If we're
                // refreshing it, then we need to clear out our storage since its handle mapped.
                mCoverArtStorage.removeImagesForDevice(mDevice);
                // Ensure the handle map is cleared since old ones are invalid on a new connection
                clearHandleUuids(mDevice);

                // Once we're connected fetch the current metadata again in case the target has an
                // image handle they can now give us. Only do this if we don't already have one.
@@ -322,14 +423,15 @@ public class AvrcpCoverArtManager {
                        + ", Code: " + status);
                return;
            }
            String imageUuid = getUuidForHandle(mDevice, imageHandle);
            debug(mDevice.getAddress() + ": Received image data for handle: " + imageHandle
                    + ", image: " + image);
            Uri uri = mCoverArtStorage.addImage(mDevice, imageHandle, image.getImage());
                    + ", uuid: " + imageUuid + ", image: " + image);
            Uri uri = mCoverArtStorage.addImage(mDevice, imageUuid, image.getImage());
            if (uri == null) {
                error("Could not store downloaded image");
                return;
            }
            DownloadEvent event = new DownloadEvent(imageHandle, uri);
            DownloadEvent event = new DownloadEvent(imageUuid, uri);
            if (mCallback != null) mCallback.onImageDownloadComplete(mDevice, event);
        }
    }
@@ -340,7 +442,13 @@ public class AvrcpCoverArtManager {
        s += "    Download Scheme: " + mDownloadScheme + "\n";
        for (BluetoothDevice device : mClients.keySet()) {
            AvrcpBipClient client = getClient(device);
            s += "    " + client.toString() + "\n";
            AvrcpBipSession session = getSession(device);
            s += "    " + device.getAddress() + ":" + "\n";
            s += "      Client: " + client.toString() + "\n";
            s += "      Handles: " + "\n";
            for (String handle : session.getSessionHandles()) {
                s += "        " + handle + " -> " + session.getHandleUuid(handle) + "\n";
            }
        }
        s += "  " + mCoverArtStorage.toString();
        return s;
+14 −2
Original line number Diff line number Diff line
@@ -89,7 +89,10 @@ public class AvrcpItem {
    // Our own book keeping value since database unaware players sometimes send repeat UIDs.
    private String mUuid;

    // Our owned internal Uri value that points to downloaded cover art image
    // A status to indicate if the image at the URI is downloaded and cached
    private String mImageUuid = null;

    // Our own internal Uri value that points to downloaded cover art image
    private Uri mImageUri;

    private AvrcpItem() {
@@ -159,6 +162,14 @@ public class AvrcpItem {
        return mCoverArtHandle;
    }

    public String getCoverArtUuid() {
        return mImageUuid;
    }

    public void setCoverArtUuid(String uuid) {
        mImageUuid = uuid;
    }

    public synchronized Uri getCoverArtLocation() {
        return mImageUri;
    }
@@ -226,7 +237,8 @@ public class AvrcpItem {
        return "AvrcpItem{mUuid=" + mUuid + ", mUid=" + mUid + ", mItemType=" + mItemType
                + ", mType=" + mType + ", mDisplayableName=" + mDisplayableName
                + ", mTitle=" + mTitle + ", mPlayable=" + mPlayable + ", mBrowsable="
                + mBrowsable + ", mCoverArtHandle=" + getCoverArtHandle() + "}";
                + mBrowsable + ", mCoverArtHandle=" + getCoverArtHandle()
                + ", mImageUuid=" + mImageUuid + ", mImageUri" + mImageUri + "}";
    }

    @Override
+5 −5
Original line number Diff line number Diff line
@@ -186,12 +186,12 @@ class AvrcpPlayer {
        mCurrentTrack = update;
    }

    public synchronized boolean notifyImageDownload(String handle, Uri imageUri) {
        if (DBG) Log.d(TAG, "Got an image download -- handle=" + handle + ", uri=" + imageUri);
        if (handle == null || imageUri == null || mCurrentTrack == null) return false;
        if (handle.equals(mCurrentTrack.getCoverArtHandle())) {
    public synchronized boolean notifyImageDownload(String uuid, Uri imageUri) {
        if (DBG) Log.d(TAG, "Got an image download -- uuid=" + uuid + ", uri=" + imageUri);
        if (uuid == null || imageUri == null || mCurrentTrack == null) return false;
        if (uuid.equals(mCurrentTrack.getCoverArtUuid())) {
            mCurrentTrack.setCoverArtLocation(imageUri);
            if (DBG) Log.d(TAG, "Handle '" + handle + "' was added to current track.");
            if (DBG) Log.d(TAG, "Image UUID '" + uuid + "' was added to current track.");
            return true;
        }
        return false;
Loading