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

Commit e8f4a32e authored by Sal Savage's avatar Sal Savage Committed by Gerrit Code Review
Browse files

Merge changes Ib670834d,I4340de33

* changes:
  Validate the image handle we parse before using it
  Null check data before stringifying/serializing and check validity
parents 9ee75fc0 848b202c
Loading
Loading
Loading
Loading
+23 −2
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ public class AvrcpCoverArtManager {
        }

        public String getHandleUuid(String handle) {
            if (handle == null) return null;
            if (isValidImageHandle(handle)) return null;
            String newUuid = UUID.randomUUID().toString();
            String existingUuid = mUuids.putIfAbsent(handle, newUuid);
            if (existingUuid != null) return existingUuid;
@@ -121,6 +121,24 @@ public class AvrcpCoverArtManager {
        }
    }

    /**
     * Validate an image handle meets the AVRCP and BIP specifications
     *
     * By the BIP specification that AVRCP uses, "Image handles are 7 character long strings
     * containing only the digits 0 to 9."
     *
     * @return True if the input string is a valid image handle
     */
    public static boolean isValidImageHandle(String handle) {
        if (handle == null || handle.length() != 7) return false;
        for (char c : handle.toCharArray()) {
            if (!Character.isDigit(c)) {
                return false;
            }
        }
        return true;
    }

    public AvrcpCoverArtManager(AvrcpControllerService service, Callback callback) {
        mService = service;
        mCoverArtStorage = new AvrcpCoverArtStorage(mService);
@@ -225,7 +243,7 @@ public class AvrcpCoverArtManager {
     */
    public String getUuidForHandle(BluetoothDevice device, String handle) {
        AvrcpBipSession session = getSession(device);
        if (session == null || handle == null) return null;
        if (session == null || !isValidImageHandle(handle)) return null;
        return session.getHandleUuid(handle);
    }

@@ -359,6 +377,9 @@ public class AvrcpCoverArtManager {
     * @return A descriptor containing the desirable download format
     */
    private BipImageDescriptor determineImageDescriptor(BipImageProperties properties) {
        if (properties == null || !properties.isValid()) {
            warn("Provided properties don't meet the spec. Requesting thumbnail format anyway.");
        }
        BipImageDescriptor.Builder builder = new BipImageDescriptor.Builder();
        switch (mDownloadScheme) {
            // BIP Specification says a blank/null descriptor signals to pull the native format
+7 −3
Original line number Diff line number Diff line
@@ -232,6 +232,10 @@ public class AvrcpItem {
        return new MediaItem(descriptionBuilder.build(), flags);
    }

    private static String parseImageHandle(String handle) {
        return AvrcpCoverArtManager.isValidImageHandle(handle) ? handle : null;
    }

    @Override
    public String toString() {
        return "AvrcpItem{mUuid=" + mUuid + ", mUid=" + mUid + ", mItemType=" + mItemType
@@ -338,7 +342,7 @@ public class AvrcpItem {
                        }
                        break;
                    case MEDIA_ATTRIBUTE_COVER_ART_HANDLE:
                        mAvrcpItem.mCoverArtHandle = attrMap[i];
                        mAvrcpItem.mCoverArtHandle = parseImageHandle(attrMap[i]);
                        break;
                }
            }
@@ -515,13 +519,13 @@ public class AvrcpItem {
        }

        /**
         * Set the cover art handle for the AvrcpItem you are building
         * Set the cover art handle for the AvrcpItem you are building.
         *
         * @param coverArtHandle The cover art image handle provided by a remote device
         * @return This object, so you can continue building
         */
        public Builder setCoverArtHandle(String coverArtHandle) {
            mAvrcpItem.mCoverArtHandle = coverArtHandle;
            mAvrcpItem.mCoverArtHandle = parseImageHandle(coverArtHandle);
            return this;
        }

+64 −2
Original line number Diff line number Diff line
@@ -43,6 +43,13 @@ import java.util.Objects;
 * various transformations. Attachments describes other items that can be downloaded that are
 * associated with the image (text, sounds, etc.)
 *
 * The specification requires that
 *     1. The fixed version string of "1.0" is used
 *     2. There is an image handle
 *     3. The "imaging thumbnail format" is included. This is defined for BIP in section 4.4.3
 *        (160x120 JPEG) and redefined for AVRCP in section 5.14.2.2.1 I (200x200 JPEG). It can be
 *        either a native or variant format.
 *
 * Example:
 *     <image-properties version="1.0" handle="123456789">
 *     <native encoding="JPEG" pixel="1280*1024" size="1048576"/>
@@ -142,6 +149,11 @@ public class BipImageProperties {
     */
    private String mFriendlyName = null;

    /**
     * Whether we have the required imaging thumbnail format
     */
    private boolean mHasThumbnailFormat = false;

    /**
     * The various sets of available formats.
     */
@@ -220,6 +232,10 @@ public class BipImageProperties {
        return mImageHandle;
    }

    public String getVersion() {
        return mVersion;
    }

    public String getFriendlyName() {
        return mFriendlyName;
    }
@@ -243,6 +259,10 @@ public class BipImageProperties {
                    + "' but expected '" + BipImageFormat.FORMAT_NATIVE + "'");
        }
        mNativeFormats.add(format);

        if (!mHasThumbnailFormat && isThumbnailFormat(format)) {
            mHasThumbnailFormat = true;
        }
    }

    private void addVariantFormat(BipImageFormat format) {
@@ -252,6 +272,29 @@ public class BipImageProperties {
                    + "' but expected '" + BipImageFormat.FORMAT_VARIANT + "'");
        }
        mVariantFormats.add(format);

        if (!mHasThumbnailFormat && isThumbnailFormat(format)) {
            mHasThumbnailFormat = true;
        }
    }

    private boolean isThumbnailFormat(BipImageFormat format) {
        if (format == null) return false;

        BipEncoding encoding = format.getEncoding();
        if (encoding == null || encoding.getType() != BipEncoding.JPEG) return false;

        BipPixel pixel = format.getPixel();
        if (pixel == null) return false;
        switch (pixel.getType()) {
            case BipPixel.TYPE_FIXED:
                return pixel.getMaxWidth() == 200 && pixel.getMaxHeight() == 200;
            case BipPixel.TYPE_RESIZE_MODIFIED_ASPECT_RATIO:
                return pixel.getMaxWidth() >= 200 && pixel.getMaxHeight() >= 200;
            case BipPixel.TYPE_RESIZE_FIXED_ASPECT_RATIO:
                return pixel.getMaxWidth() == pixel.getMaxHeight() && pixel.getMaxWidth() >= 200;
        }
        return false;
    }

    private void addAttachment(BipAttachmentFormat format) {
@@ -268,8 +311,11 @@ public class BipImageProperties {
            xmlMsgElement.startDocument("UTF-8", true);
            xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
            xmlMsgElement.startTag(null, "image-properties");
            xmlMsgElement.attribute(null, "version", mVersion);
            xmlMsgElement.attribute(null, "handle", mImageHandle);
            if (mVersion != null) xmlMsgElement.attribute(null, "version", mVersion);
            if (mImageHandle != null) xmlMsgElement.attribute(null, "handle", mImageHandle);
            if (mFriendlyName != null) {
                xmlMsgElement.attribute(null, "friendly-name", mFriendlyName);
            }

            for (BipImageFormat format : mNativeFormats) {
                BipEncoding encoding = format.getEncoding();
@@ -354,9 +400,12 @@ public class BipImageProperties {
    /**
     * Serialize this object into a byte array
     *
     * Objects that are not valid will fail to serialize and return null.
     *
     * @return Byte array representing this object, ready to send over OBEX, or null on error.
     */
    public byte[] serialize() {
        if (!isValid()) return null;
        String s = toString();
        try {
            return s != null ? s.getBytes("UTF-8") : null;
@@ -365,6 +414,19 @@ public class BipImageProperties {
        }
    }

    /**
     * Determine if the contents of this BipImageProperties object are valid and meet the
     * specification requirements:
     *     1. Include the fixed 1.0 version
     *     2. Include an image handle
     *     3. Have the thumbnail format as either the native or variant
     *
     * @return True if our contents are valid, false otherwise
     */
    public boolean isValid() {
        return sVersion.equals(mVersion) && mImageHandle != null && mHasThumbnailFormat;
    }

    private static void warn(String msg) {
        Log.w(TAG, msg);
    }
+213 −5
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ public final class AvrcpItemTest {
        long totalTracks = 12;
        String genre = "Viking Metal";
        long playingTime = 301;
        String artHandle = "abc123";
        String artHandle = "0000001";
        Uri uri = Uri.parse("content://somewhere");
        Uri uri2 = Uri.parse("content://somewhereelse");

@@ -119,7 +119,7 @@ public final class AvrcpItemTest {
        String totalTracks = "12";
        String genre = "Viking Metal";
        String playingTime = "301";
        String artHandle = "abc123";
        String artHandle = "0000001";

        int[] attrIds = new int[]{
            MEDIA_ATTRIBUTE_TITLE,
@@ -171,7 +171,7 @@ public final class AvrcpItemTest {
        String totalTracks = "12";
        String genre = "Viking Metal";
        String playingTime = "301";
        String artHandle = "abc123";
        String artHandle = "0000001";

        int[] attrIds = new int[]{
            MEDIA_ATTRIBUTE_TITLE,
@@ -222,6 +222,214 @@ public final class AvrcpItemTest {
        Assert.assertEquals(null, item.getCoverArtLocation());
    }

    @Test
    public void buildAvrcpItemFromAvrcpAttributes_imageHandleTooShort() {
        String title = "Aaaaargh";
        String artist = "Bluetooth";
        String album = "The Best Protocol";
        String trackNumber = "1";
        String totalTracks = "12";
        String genre = "Viking Metal";
        String playingTime = "301";
        String artHandle = "000001"; // length 6 and not 7

        int[] attrIds = new int[]{
            MEDIA_ATTRIBUTE_TITLE,
            MEDIA_ATTRIBUTE_ARTIST_NAME,
            MEDIA_ATTRIBUTE_ALBUM_NAME,
            MEDIA_ATTRIBUTE_TRACK_NUMBER,
            MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER,
            MEDIA_ATTRIBUTE_GENRE,
            MEDIA_ATTRIBUTE_PLAYING_TIME,
            MEDIA_ATTRIBUTE_COVER_ART_HANDLE
        };

        String[] attrMap = new String[]{
            title,
            artist,
            album,
            trackNumber,
            totalTracks,
            genre,
            playingTime,
            artHandle
        };

        AvrcpItem.Builder builder = new AvrcpItem.Builder();
        builder.fromAvrcpAttributeArray(attrIds, attrMap);
        AvrcpItem item = builder.build();

        Assert.assertEquals(null, item.getDevice());
        Assert.assertEquals(false, item.isPlayable());
        Assert.assertEquals(false, item.isBrowsable());
        Assert.assertEquals(0, item.getUid());
        Assert.assertEquals(null, item.getUuid());
        Assert.assertEquals(null, item.getDisplayableName());
        Assert.assertEquals(title, item.getTitle());
        Assert.assertEquals(artist, item.getArtistName());
        Assert.assertEquals(album, item.getAlbumName());
        Assert.assertEquals(1, item.getTrackNumber());
        Assert.assertEquals(12, item.getTotalNumberOfTracks());
        Assert.assertEquals(null, item.getCoverArtHandle());
        Assert.assertEquals(null, item.getCoverArtLocation());
    }

    @Test
    public void buildAvrcpItemFromAvrcpAttributes_imageHandleEmpty() {
        String title = "Aaaaargh";
        String artist = "Bluetooth";
        String album = "The Best Protocol";
        String trackNumber = "1";
        String totalTracks = "12";
        String genre = "Viking Metal";
        String playingTime = "301";
        String artHandle = "";

        int[] attrIds = new int[]{
            MEDIA_ATTRIBUTE_TITLE,
            MEDIA_ATTRIBUTE_ARTIST_NAME,
            MEDIA_ATTRIBUTE_ALBUM_NAME,
            MEDIA_ATTRIBUTE_TRACK_NUMBER,
            MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER,
            MEDIA_ATTRIBUTE_GENRE,
            MEDIA_ATTRIBUTE_PLAYING_TIME,
            MEDIA_ATTRIBUTE_COVER_ART_HANDLE
        };

        String[] attrMap = new String[]{
            title,
            artist,
            album,
            trackNumber,
            totalTracks,
            genre,
            playingTime,
            artHandle
        };

        AvrcpItem.Builder builder = new AvrcpItem.Builder();
        builder.fromAvrcpAttributeArray(attrIds, attrMap);
        AvrcpItem item = builder.build();

        Assert.assertEquals(null, item.getDevice());
        Assert.assertEquals(false, item.isPlayable());
        Assert.assertEquals(false, item.isBrowsable());
        Assert.assertEquals(0, item.getUid());
        Assert.assertEquals(null, item.getUuid());
        Assert.assertEquals(null, item.getDisplayableName());
        Assert.assertEquals(title, item.getTitle());
        Assert.assertEquals(artist, item.getArtistName());
        Assert.assertEquals(album, item.getAlbumName());
        Assert.assertEquals(1, item.getTrackNumber());
        Assert.assertEquals(12, item.getTotalNumberOfTracks());
        Assert.assertEquals(null, item.getCoverArtHandle());
        Assert.assertEquals(null, item.getCoverArtLocation());
    }

    @Test
    public void buildAvrcpItemFromAvrcpAttributes_imageHandleNull() {
        String title = "Aaaaargh";
        String artist = "Bluetooth";
        String album = "The Best Protocol";
        String trackNumber = "1";
        String totalTracks = "12";
        String genre = "Viking Metal";
        String playingTime = "301";
        String artHandle = null;

        int[] attrIds = new int[]{
            MEDIA_ATTRIBUTE_TITLE,
            MEDIA_ATTRIBUTE_ARTIST_NAME,
            MEDIA_ATTRIBUTE_ALBUM_NAME,
            MEDIA_ATTRIBUTE_TRACK_NUMBER,
            MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER,
            MEDIA_ATTRIBUTE_GENRE,
            MEDIA_ATTRIBUTE_PLAYING_TIME,
            MEDIA_ATTRIBUTE_COVER_ART_HANDLE
        };

        String[] attrMap = new String[]{
            title,
            artist,
            album,
            trackNumber,
            totalTracks,
            genre,
            playingTime,
            artHandle
        };

        AvrcpItem.Builder builder = new AvrcpItem.Builder();
        builder.fromAvrcpAttributeArray(attrIds, attrMap);
        AvrcpItem item = builder.build();

        Assert.assertEquals(null, item.getDevice());
        Assert.assertEquals(false, item.isPlayable());
        Assert.assertEquals(false, item.isBrowsable());
        Assert.assertEquals(0, item.getUid());
        Assert.assertEquals(null, item.getUuid());
        Assert.assertEquals(null, item.getDisplayableName());
        Assert.assertEquals(title, item.getTitle());
        Assert.assertEquals(artist, item.getArtistName());
        Assert.assertEquals(album, item.getAlbumName());
        Assert.assertEquals(1, item.getTrackNumber());
        Assert.assertEquals(12, item.getTotalNumberOfTracks());
        Assert.assertEquals(null, item.getCoverArtHandle());
        Assert.assertEquals(null, item.getCoverArtLocation());
    }

    @Test
    public void buildAvrcpItemFromAvrcpAttributes_imageHandleNotDigits() {
        String title = "Aaaaargh";
        String artist = "Bluetooth";
        String album = "The Best Protocol";
        String trackNumber = "1";
        String totalTracks = "12";
        String genre = "Viking Metal";
        String playingTime = "301";
        String artHandle = "123abcd";

        int[] attrIds = new int[]{
            MEDIA_ATTRIBUTE_TITLE,
            MEDIA_ATTRIBUTE_ARTIST_NAME,
            MEDIA_ATTRIBUTE_ALBUM_NAME,
            MEDIA_ATTRIBUTE_TRACK_NUMBER,
            MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER,
            MEDIA_ATTRIBUTE_GENRE,
            MEDIA_ATTRIBUTE_PLAYING_TIME,
            MEDIA_ATTRIBUTE_COVER_ART_HANDLE
        };

        String[] attrMap = new String[]{
            title,
            artist,
            album,
            trackNumber,
            totalTracks,
            genre,
            playingTime,
            artHandle
        };

        AvrcpItem.Builder builder = new AvrcpItem.Builder();
        builder.fromAvrcpAttributeArray(attrIds, attrMap);
        AvrcpItem item = builder.build();

        Assert.assertEquals(null, item.getDevice());
        Assert.assertEquals(false, item.isPlayable());
        Assert.assertEquals(false, item.isBrowsable());
        Assert.assertEquals(0, item.getUid());
        Assert.assertEquals(null, item.getUuid());
        Assert.assertEquals(null, item.getDisplayableName());
        Assert.assertEquals(title, item.getTitle());
        Assert.assertEquals(artist, item.getArtistName());
        Assert.assertEquals(album, item.getAlbumName());
        Assert.assertEquals(1, item.getTrackNumber());
        Assert.assertEquals(12, item.getTotalNumberOfTracks());
        Assert.assertEquals(null, item.getCoverArtHandle());
        Assert.assertEquals(null, item.getCoverArtLocation());
    }

    @Test
    public void updateCoverArtLocation() {
        Uri uri = Uri.parse("content://somewhere");
@@ -246,7 +454,7 @@ public final class AvrcpItemTest {
        long totalTracks = 12;
        String genre = "Viking Metal";
        long playingTime = 301;
        String artHandle = "abc123";
        String artHandle = "0000001";
        Uri uri = Uri.parse("content://somewhere");

        AvrcpItem.Builder builder = new AvrcpItem.Builder();
@@ -303,7 +511,7 @@ public final class AvrcpItemTest {
        long totalTracks = 12;
        String genre = "Viking Metal";
        long playingTime = 301;
        String artHandle = "abc123";
        String artHandle = "0000001";
        Uri uri = Uri.parse("content://somewhere");
        int type = AvrcpItem.FOLDER_TITLES;

+351 −22

File changed.

Preview size limit exceeded, changes collapsed.