• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bluetooth.avrcpcontroller;
18 
19 import android.bluetooth.BluetoothDevice;
20 import android.net.Uri;
21 import android.os.Bundle;
22 import android.support.v4.media.MediaBrowserCompat.MediaItem;
23 import android.support.v4.media.MediaDescriptionCompat;
24 import android.support.v4.media.MediaMetadataCompat;
25 import android.util.Log;
26 
27 import java.util.Objects;
28 
29 /**
30  * An object representing a single item returned from an AVRCP folder listing in the VFS scope.
31  *
32  * <p>This object knows how to turn itself into each of the Android Media Framework objects so the
33  * metadata can easily be shared with the system.
34  */
35 public class AvrcpItem {
36     private static final String TAG = AvrcpItem.class.getSimpleName();
37 
38     // AVRCP Specification defined item types
39     public static final int TYPE_PLAYER = 0x1;
40     public static final int TYPE_FOLDER = 0x2;
41     public static final int TYPE_MEDIA = 0x3;
42 
43     // AVRCP Specification defined folder item sub types. These match with the Media Framework's
44     // definition of the constants as well.
45     public static final int FOLDER_MIXED = 0x00;
46     public static final int FOLDER_TITLES = 0x01;
47     public static final int FOLDER_ALBUMS = 0x02;
48     public static final int FOLDER_ARTISTS = 0x03;
49     public static final int FOLDER_GENRES = 0x04;
50     public static final int FOLDER_PLAYLISTS = 0x05;
51     public static final int FOLDER_YEARS = 0x06;
52 
53     // AVRCP Specification defined media item sub types
54     public static final int MEDIA_AUDIO = 0x00;
55     public static final int MEDIA_VIDEO = 0x01;
56 
57     // Keys for packaging extra data with MediaItems
58     public static final String AVRCP_ITEM_KEY_UID = "avrcp-item-key-uid";
59 
60     // Type of item, one of [TYPE_PLAYER, TYPE_FOLDER, TYPE_MEDIA]
61     private int mItemType;
62 
63     // Sub type of item, dependant on whether it's a folder or media item
64     // Folder -> FOLDER_* constants
65     // Media -> MEDIA_* constants
66     private int mType;
67 
68     // Bluetooth Device this piece of metadata came from
69     private BluetoothDevice mDevice;
70 
71     // AVRCP Specification defined metadata for browsed media items
72     private long mUid;
73     private String mDisplayableName;
74 
75     // AVRCP Specification defined set of available attributes
76     private String mTitle;
77     private String mArtistName;
78     private String mAlbumName;
79     private long mTrackNumber;
80     private long mTotalNumberOfTracks;
81     private String mGenre;
82     private long mPlayingTime;
83     private String mCoverArtHandle;
84 
85     private boolean mPlayable = false;
86     private boolean mBrowsable = false;
87 
88     // Our own book keeping value since database unaware players sometimes send repeat UIDs.
89     private String mUuid;
90 
91     // A status to indicate if the image at the URI is downloaded and cached
92     private String mImageUuid = null;
93 
94     // Our own internal Uri value that points to downloaded cover art image
95     private Uri mImageUri;
96 
AvrcpItem()97     private AvrcpItem() {}
98 
getDevice()99     public BluetoothDevice getDevice() {
100         return mDevice;
101     }
102 
getUid()103     public long getUid() {
104         return mUid;
105     }
106 
getUuid()107     public String getUuid() {
108         return mUuid;
109     }
110 
getItemType()111     public int getItemType() {
112         return mItemType;
113     }
114 
getType()115     public int getType() {
116         return mType;
117     }
118 
getDisplayableName()119     public String getDisplayableName() {
120         return mDisplayableName;
121     }
122 
getTitle()123     public String getTitle() {
124         return mTitle;
125     }
126 
getArtistName()127     public String getArtistName() {
128         return mArtistName;
129     }
130 
getAlbumName()131     public String getAlbumName() {
132         return mAlbumName;
133     }
134 
getTrackNumber()135     public long getTrackNumber() {
136         return mTrackNumber;
137     }
138 
getTotalNumberOfTracks()139     public long getTotalNumberOfTracks() {
140         return mTotalNumberOfTracks;
141     }
142 
getGenre()143     public String getGenre() {
144         return mGenre;
145     }
146 
getPlayingTime()147     public long getPlayingTime() {
148         return mPlayingTime;
149     }
150 
isPlayable()151     public boolean isPlayable() {
152         return mPlayable;
153     }
154 
isBrowsable()155     public boolean isBrowsable() {
156         return mBrowsable;
157     }
158 
getCoverArtHandle()159     public String getCoverArtHandle() {
160         return mCoverArtHandle;
161     }
162 
getCoverArtUuid()163     public String getCoverArtUuid() {
164         return mImageUuid;
165     }
166 
setCoverArtUuid(String uuid)167     public void setCoverArtUuid(String uuid) {
168         mImageUuid = uuid;
169     }
170 
getCoverArtLocation()171     public synchronized Uri getCoverArtLocation() {
172         return mImageUri;
173     }
174 
setCoverArtLocation(Uri uri)175     public synchronized void setCoverArtLocation(Uri uri) {
176         mImageUri = uri;
177     }
178 
179     /** Convert this item an Android Media Framework MediaMetadata */
toMediaMetadata()180     public MediaMetadataCompat toMediaMetadata() {
181         MediaMetadataCompat.Builder metaDataBuilder = new MediaMetadataCompat.Builder();
182         Uri coverArtUri = getCoverArtLocation();
183         String uriString = coverArtUri != null ? coverArtUri.toString() : null;
184         metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mUuid);
185         metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, mDisplayableName);
186         metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, mTitle);
187         metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, mArtistName);
188         metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, mAlbumName);
189         metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, mTrackNumber);
190         metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, mTotalNumberOfTracks);
191         metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_GENRE, mGenre);
192         metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, mPlayingTime);
193         metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, uriString);
194         metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, uriString);
195         metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, uriString);
196         if (mItemType == TYPE_FOLDER) {
197             metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE, mType);
198         }
199         return metaDataBuilder.build();
200     }
201 
202     /** Convert this item an Android Media Framework MediaItem */
toMediaItem()203     public MediaItem toMediaItem() {
204         MediaDescriptionCompat.Builder descriptionBuilder = new MediaDescriptionCompat.Builder();
205 
206         descriptionBuilder.setMediaId(mUuid);
207 
208         String name = null;
209         if (mDisplayableName != null) {
210             name = mDisplayableName;
211         } else if (mTitle != null) {
212             name = mTitle;
213         }
214         descriptionBuilder.setTitle(name);
215 
216         descriptionBuilder.setIconUri(getCoverArtLocation());
217 
218         Bundle extras = new Bundle();
219         extras.putLong(AVRCP_ITEM_KEY_UID, mUid);
220         descriptionBuilder.setExtras(extras);
221 
222         int flags = 0x0;
223         if (mPlayable) flags |= MediaItem.FLAG_PLAYABLE;
224         if (mBrowsable) flags |= MediaItem.FLAG_BROWSABLE;
225 
226         return new MediaItem(descriptionBuilder.build(), flags);
227     }
228 
parseImageHandle(String handle)229     private static String parseImageHandle(String handle) {
230         return AvrcpCoverArtManager.isValidImageHandle(handle) ? handle : null;
231     }
232 
233     @Override
toString()234     public String toString() {
235         return "AvrcpItem{mUuid="
236                 + mUuid
237                 + ", mUid="
238                 + mUid
239                 + ", mItemType="
240                 + mItemType
241                 + ", mType="
242                 + mType
243                 + ", mDisplayableName="
244                 + mDisplayableName
245                 + ", mTitle="
246                 + mTitle
247                 + " mPlayingTime="
248                 + mPlayingTime
249                 + " mTrack="
250                 + mTrackNumber
251                 + "/"
252                 + mTotalNumberOfTracks
253                 + ", mPlayable="
254                 + mPlayable
255                 + ", mBrowsable="
256                 + mBrowsable
257                 + ", mCoverArtHandle="
258                 + getCoverArtHandle()
259                 + ", mImageUuid="
260                 + mImageUuid
261                 + ", mImageUri"
262                 + mImageUri
263                 + "}";
264     }
265 
266     @Override
equals(Object o)267     public boolean equals(Object o) {
268         if (this == o) {
269             return true;
270         }
271 
272         if (!(o instanceof AvrcpItem other)) {
273             return false;
274         }
275 
276         return Objects.equals(mUuid, other.getUuid())
277                 && Objects.equals(mDevice, other.getDevice())
278                 && mUid == other.getUid()
279                 && mItemType == other.getItemType()
280                 && mType == other.getType()
281                 && Objects.equals(mTitle, other.getTitle())
282                 && Objects.equals(mDisplayableName, other.getDisplayableName())
283                 && Objects.equals(mArtistName, other.getArtistName())
284                 && Objects.equals(mAlbumName, other.getAlbumName())
285                 && mTrackNumber == other.getTrackNumber()
286                 && mTotalNumberOfTracks == other.getTotalNumberOfTracks()
287                 && Objects.equals(mGenre, other.getGenre())
288                 && mPlayingTime == other.getPlayingTime()
289                 && Objects.equals(mCoverArtHandle, other.getCoverArtHandle())
290                 && mPlayable == other.isPlayable()
291                 && mBrowsable == other.isBrowsable()
292                 && Objects.equals(mImageUri, other.getCoverArtLocation());
293     }
294 
295     @Override
hashCode()296     public int hashCode() {
297         return Objects.hash(mUuid);
298     }
299 
300     /** Builder for an AvrcpItem */
301     public static class Builder {
302         private static final String TAG = AvrcpItem.TAG + "." + Builder.class.getSimpleName();
303 
304         // Attribute ID Values from AVRCP Specification
305         private static final int MEDIA_ATTRIBUTE_TITLE = 0x01;
306         private static final int MEDIA_ATTRIBUTE_ARTIST_NAME = 0x02;
307         private static final int MEDIA_ATTRIBUTE_ALBUM_NAME = 0x03;
308         private static final int MEDIA_ATTRIBUTE_TRACK_NUMBER = 0x04;
309         private static final int MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER = 0x05;
310         private static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
311         private static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
312         private static final int MEDIA_ATTRIBUTE_COVER_ART_HANDLE = 0x08;
313 
314         private final AvrcpItem mAvrcpItem = new AvrcpItem();
315 
316         /**
317          * Initialize all relevant AvrcpItem internals from the AVRCP specification defined set of
318          * item attributes
319          *
320          * @param attrIds The array of AVRCP specification defined IDs in the order they match to
321          *     the value string attrMap
322          * @param attrMap The mapped values for each ID
323          * @return This object so you can continue building
324          */
fromAvrcpAttributeArray(int[] attrIds, String[] attrMap)325         public Builder fromAvrcpAttributeArray(int[] attrIds, String[] attrMap) {
326             int attributeCount = Math.max(attrIds.length, attrMap.length);
327             for (int i = 0; i < attributeCount; i++) {
328                 Log.d(TAG, attrIds[i] + " = " + attrMap[i]);
329                 switch (attrIds[i]) {
330                     case MEDIA_ATTRIBUTE_TITLE:
331                         mAvrcpItem.mTitle = attrMap[i];
332                         break;
333                     case MEDIA_ATTRIBUTE_ARTIST_NAME:
334                         mAvrcpItem.mArtistName = attrMap[i];
335                         break;
336                     case MEDIA_ATTRIBUTE_ALBUM_NAME:
337                         mAvrcpItem.mAlbumName = attrMap[i];
338                         break;
339                     case MEDIA_ATTRIBUTE_TRACK_NUMBER:
340                         try {
341                             mAvrcpItem.mTrackNumber = Long.valueOf(attrMap[i]);
342                         } catch (java.lang.NumberFormatException e) {
343                             // If Track Number doesn't parse, leave it unset
344                         }
345                         break;
346                     case MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
347                         try {
348                             mAvrcpItem.mTotalNumberOfTracks = Long.valueOf(attrMap[i]);
349                         } catch (java.lang.NumberFormatException e) {
350                             // If Total Track Number doesn't parse, leave it unset
351                         }
352                         break;
353                     case MEDIA_ATTRIBUTE_GENRE:
354                         mAvrcpItem.mGenre = attrMap[i];
355                         break;
356                     case MEDIA_ATTRIBUTE_PLAYING_TIME:
357                         try {
358                             mAvrcpItem.mPlayingTime = Long.valueOf(attrMap[i]);
359                         } catch (java.lang.NumberFormatException e) {
360                             // If Playing Time doesn't parse, leave it unset
361                         }
362                         break;
363                     case MEDIA_ATTRIBUTE_COVER_ART_HANDLE:
364                         mAvrcpItem.mCoverArtHandle = parseImageHandle(attrMap[i]);
365                         break;
366                 }
367             }
368             return this;
369         }
370 
371         /**
372          * Set the item type for the AvrcpItem you are building
373          *
374          * <p>Type can be one of PLAYER, FOLDER, or MEDIA
375          *
376          * @param itemType The item type as an AvrcpItem.* type value
377          * @return This object, so you can continue building
378          */
setItemType(int itemType)379         public Builder setItemType(int itemType) {
380             mAvrcpItem.mItemType = itemType;
381             return this;
382         }
383 
384         /**
385          * Set the type for the AvrcpItem you are building
386          *
387          * <p>This is the type of the PLAYER, FOLDER, or MEDIA item.
388          *
389          * @param type The type as one of the AvrcpItem.MEDIA_* or FOLDER_* types
390          * @return This object, so you can continue building
391          */
setType(int type)392         public Builder setType(int type) {
393             mAvrcpItem.mType = type;
394             return this;
395         }
396 
397         /**
398          * Set the device for the AvrcpItem you are building
399          *
400          * @param device The BluetoothDevice object that this item came from
401          * @return This object, so you can continue building
402          */
setDevice(BluetoothDevice device)403         public Builder setDevice(BluetoothDevice device) {
404             mAvrcpItem.mDevice = device;
405             return this;
406         }
407 
408         /**
409          * Note that the AvrcpItem you are building is playable
410          *
411          * @param playable True if playable, false otherwise
412          * @return This object, so you can continue building
413          */
setPlayable(boolean playable)414         public Builder setPlayable(boolean playable) {
415             mAvrcpItem.mPlayable = playable;
416             return this;
417         }
418 
419         /**
420          * Note that the AvrcpItem you are building is browsable
421          *
422          * @param browsable True if browsable, false otherwise
423          * @return This object, so you can continue building
424          */
setBrowsable(boolean browsable)425         public Builder setBrowsable(boolean browsable) {
426             mAvrcpItem.mBrowsable = browsable;
427             return this;
428         }
429 
430         /**
431          * Set the AVRCP defined UID assigned to the AvrcpItem you are building
432          *
433          * @param uid The UID given to this item by the remote device
434          * @return This object, so you can continue building
435          */
setUid(long uid)436         public Builder setUid(long uid) {
437             mAvrcpItem.mUid = uid;
438             return this;
439         }
440 
441         /**
442          * Set the UUID you wish to associate with the AvrcpItem you are building
443          *
444          * @param uuid A string UUID value
445          * @return This object, so you can continue building
446          */
setUuid(String uuid)447         public Builder setUuid(String uuid) {
448             mAvrcpItem.mUuid = uuid;
449             return this;
450         }
451 
452         /**
453          * Set the displayable name for the AvrcpItem you are building
454          *
455          * @param displayableName A string representing a friendly, displayable name
456          * @return This object, so you can continue building
457          */
setDisplayableName(String displayableName)458         public Builder setDisplayableName(String displayableName) {
459             mAvrcpItem.mDisplayableName = displayableName;
460             return this;
461         }
462 
463         /**
464          * Set the title for the AvrcpItem you are building
465          *
466          * @param title The title as a string
467          * @return This object, so you can continue building
468          */
setTitle(String title)469         public Builder setTitle(String title) {
470             mAvrcpItem.mTitle = title;
471             return this;
472         }
473 
474         /**
475          * Set the artist name for the AvrcpItem you are building
476          *
477          * @param artistName The artist name as a string
478          * @return This object, so you can continue building
479          */
setArtistName(String artistName)480         public Builder setArtistName(String artistName) {
481             mAvrcpItem.mArtistName = artistName;
482             return this;
483         }
484 
485         /**
486          * Set the album name for the AvrcpItem you are building
487          *
488          * @param albumName The album name as a string
489          * @return This object, so you can continue building
490          */
setAlbumName(String albumName)491         public Builder setAlbumName(String albumName) {
492             mAvrcpItem.mAlbumName = albumName;
493             return this;
494         }
495 
496         /**
497          * Set the track number for the AvrcpItem you are building
498          *
499          * @param trackNumber The track number
500          * @return This object, so you can continue building
501          */
setTrackNumber(long trackNumber)502         public Builder setTrackNumber(long trackNumber) {
503             mAvrcpItem.mTrackNumber = trackNumber;
504             return this;
505         }
506 
507         /**
508          * Set the total number of tracks on the playlist or album that this AvrcpItem is on
509          *
510          * @param totalNumberOfTracks The total number of tracks along side this item
511          * @return This object, so you can continue building
512          */
setTotalNumberOfTracks(long totalNumberOfTracks)513         public Builder setTotalNumberOfTracks(long totalNumberOfTracks) {
514             mAvrcpItem.mTotalNumberOfTracks = totalNumberOfTracks;
515             return this;
516         }
517 
518         /**
519          * Set the genre name for the AvrcpItem you are building
520          *
521          * @param genre The genre as a string
522          * @return This object, so you can continue building
523          */
setGenre(String genre)524         public Builder setGenre(String genre) {
525             mAvrcpItem.mGenre = genre;
526             return this;
527         }
528 
529         /**
530          * Set the total playing time for the AvrcpItem you are building
531          *
532          * @param playingTime The playing time in seconds
533          * @return This object, so you can continue building
534          */
setPlayingTime(long playingTime)535         public Builder setPlayingTime(long playingTime) {
536             mAvrcpItem.mPlayingTime = playingTime;
537             return this;
538         }
539 
540         /**
541          * Set the cover art handle for the AvrcpItem you are building.
542          *
543          * @param coverArtHandle The cover art image handle provided by a remote device
544          * @return This object, so you can continue building
545          */
setCoverArtHandle(String coverArtHandle)546         public Builder setCoverArtHandle(String coverArtHandle) {
547             mAvrcpItem.mCoverArtHandle = parseImageHandle(coverArtHandle);
548             return this;
549         }
550 
551         /**
552          * Set the location of the downloaded cover art for the AvrcpItem you are building
553          *
554          * @param uri The URI where our storage has placed the image associated with this item
555          * @return This object, so you can continue building
556          */
setCoverArtLocation(Uri uri)557         public Builder setCoverArtLocation(Uri uri) {
558             mAvrcpItem.setCoverArtLocation(uri);
559             return this;
560         }
561 
562         /**
563          * Build the AvrcpItem
564          *
565          * @return An AvrcpItem object
566          */
build()567         public AvrcpItem build() {
568             return mAvrcpItem;
569         }
570     }
571 }
572