1 /* 2 * Copyright (C) 2014 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 package android.support.v4.media; 17 18 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 19 20 import android.graphics.Bitmap; 21 import android.net.Uri; 22 import android.os.Build; 23 import android.os.Bundle; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.text.TextUtils; 27 28 import androidx.annotation.Nullable; 29 import androidx.annotation.RestrictTo; 30 31 /** 32 * A simple set of metadata for a media item suitable for display. This can be 33 * created using the Builder or retrieved from existing metadata using 34 * {@link MediaMetadataCompat#getDescription()}. 35 */ 36 public final class MediaDescriptionCompat implements Parcelable { 37 /** 38 * Used as a long extra field to indicate the bluetooth folder type of the media item as 39 * specified in the section 6.10.2.2 of the Bluetooth AVRCP 1.5. This is valid only for 40 * {@link MediaBrowserCompat.MediaItem} with 41 * {@link MediaBrowserCompat.MediaItem#FLAG_BROWSABLE}. The value should be one of the 42 * following: 43 * <ul> 44 * <li>{@link #BT_FOLDER_TYPE_MIXED}</li> 45 * <li>{@link #BT_FOLDER_TYPE_TITLES}</li> 46 * <li>{@link #BT_FOLDER_TYPE_ALBUMS}</li> 47 * <li>{@link #BT_FOLDER_TYPE_ARTISTS}</li> 48 * <li>{@link #BT_FOLDER_TYPE_GENRES}</li> 49 * <li>{@link #BT_FOLDER_TYPE_PLAYLISTS}</li> 50 * <li>{@link #BT_FOLDER_TYPE_YEARS}</li> 51 * </ul> 52 * 53 * @see #getExtras() 54 */ 55 public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE"; 56 57 /** 58 * The type of folder that is unknown or contains media elements of mixed types as specified in 59 * the section 6.10.2.2 of the Bluetooth AVRCP 1.5. 60 */ 61 public static final long BT_FOLDER_TYPE_MIXED = 0; 62 63 /** 64 * The type of folder that contains media elements only as specified in the section 6.10.2.2 of 65 * the Bluetooth AVRCP 1.5. 66 */ 67 public static final long BT_FOLDER_TYPE_TITLES = 1; 68 69 /** 70 * The type of folder that contains folders categorized by album as specified in the section 71 * 6.10.2.2 of the Bluetooth AVRCP 1.5. 72 */ 73 public static final long BT_FOLDER_TYPE_ALBUMS = 2; 74 75 /** 76 * The type of folder that contains folders categorized by artist as specified in the section 77 * 6.10.2.2 of the Bluetooth AVRCP 1.5. 78 */ 79 public static final long BT_FOLDER_TYPE_ARTISTS = 3; 80 81 /** 82 * The type of folder that contains folders categorized by genre as specified in the section 83 * 6.10.2.2 of the Bluetooth AVRCP 1.5. 84 */ 85 public static final long BT_FOLDER_TYPE_GENRES = 4; 86 87 /** 88 * The type of folder that contains folders categorized by playlist as specified in the section 89 * 6.10.2.2 of the Bluetooth AVRCP 1.5. 90 */ 91 public static final long BT_FOLDER_TYPE_PLAYLISTS = 5; 92 93 /** 94 * The type of folder that contains folders categorized by year as specified in the section 95 * 6.10.2.2 of the Bluetooth AVRCP 1.5. 96 */ 97 public static final long BT_FOLDER_TYPE_YEARS = 6; 98 99 /** 100 * Used as a long extra field to indicate the download status of the media item. The value 101 * should be one of the following: 102 * <ul> 103 * <li>{@link #STATUS_NOT_DOWNLOADED}</li> 104 * <li>{@link #STATUS_DOWNLOADING}</li> 105 * <li>{@link #STATUS_DOWNLOADED}</li> 106 * </ul> 107 * 108 * @see #getExtras() 109 */ 110 public static final String EXTRA_DOWNLOAD_STATUS = "android.media.extra.DOWNLOAD_STATUS"; 111 112 /** 113 * The status value to indicate the media item is not downloaded. 114 * 115 * @see #EXTRA_DOWNLOAD_STATUS 116 */ 117 public static final long STATUS_NOT_DOWNLOADED = 0; 118 119 /** 120 * The status value to indicate the media item is being downloaded. 121 * 122 * @see #EXTRA_DOWNLOAD_STATUS 123 */ 124 public static final long STATUS_DOWNLOADING = 1; 125 126 /** 127 * The status value to indicate the media item is downloaded for later offline playback. 128 * 129 * @see #EXTRA_DOWNLOAD_STATUS 130 */ 131 public static final long STATUS_DOWNLOADED = 2; 132 133 /** 134 * Custom key to store a media URI on API 21-22 devices (before it became part of the 135 * framework class) when parceling/converting to and from framework objects. 136 * 137 * @hide 138 */ 139 @RestrictTo(LIBRARY_GROUP) 140 public static final String DESCRIPTION_KEY_MEDIA_URI = 141 "android.support.v4.media.description.MEDIA_URI"; 142 /** 143 * Custom key to store whether the original Bundle provided by the developer was null 144 * 145 * @hide 146 */ 147 @RestrictTo(LIBRARY_GROUP) 148 public static final String DESCRIPTION_KEY_NULL_BUNDLE_FLAG = 149 "android.support.v4.media.description.NULL_BUNDLE_FLAG"; 150 /** 151 * A unique persistent id for the content or null. 152 */ 153 private final String mMediaId; 154 /** 155 * A primary title suitable for display or null. 156 */ 157 private final CharSequence mTitle; 158 /** 159 * A subtitle suitable for display or null. 160 */ 161 private final CharSequence mSubtitle; 162 /** 163 * A description suitable for display or null. 164 */ 165 private final CharSequence mDescription; 166 /** 167 * A bitmap icon suitable for display or null. 168 */ 169 private final Bitmap mIcon; 170 /** 171 * A Uri for an icon suitable for display or null. 172 */ 173 private final Uri mIconUri; 174 /** 175 * Extras for opaque use by apps/system. 176 */ 177 private final Bundle mExtras; 178 /** 179 * A Uri to identify this content. 180 */ 181 private final Uri mMediaUri; 182 183 /** 184 * A cached copy of the equivalent framework object. 185 */ 186 private Object mDescriptionObj; 187 MediaDescriptionCompat(String mediaId, CharSequence title, CharSequence subtitle, CharSequence description, Bitmap icon, Uri iconUri, Bundle extras, Uri mediaUri)188 MediaDescriptionCompat(String mediaId, CharSequence title, CharSequence subtitle, 189 CharSequence description, Bitmap icon, Uri iconUri, Bundle extras, Uri mediaUri) { 190 mMediaId = mediaId; 191 mTitle = title; 192 mSubtitle = subtitle; 193 mDescription = description; 194 mIcon = icon; 195 mIconUri = iconUri; 196 mExtras = extras; 197 mMediaUri = mediaUri; 198 } 199 MediaDescriptionCompat(Parcel in)200 MediaDescriptionCompat(Parcel in) { 201 mMediaId = in.readString(); 202 mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 203 mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 204 mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 205 mIcon = in.readParcelable(null); 206 mIconUri = in.readParcelable(null); 207 mExtras = in.readBundle(); 208 mMediaUri = in.readParcelable(null); 209 } 210 211 /** 212 * Returns the media id or null. See 213 * {@link MediaMetadataCompat#METADATA_KEY_MEDIA_ID}. 214 */ 215 @Nullable getMediaId()216 public String getMediaId() { 217 return mMediaId; 218 } 219 220 /** 221 * Returns a title suitable for display or null. 222 * 223 * @return A title or null. 224 */ 225 @Nullable getTitle()226 public CharSequence getTitle() { 227 return mTitle; 228 } 229 230 /** 231 * Returns a subtitle suitable for display or null. 232 * 233 * @return A subtitle or null. 234 */ 235 @Nullable getSubtitle()236 public CharSequence getSubtitle() { 237 return mSubtitle; 238 } 239 240 /** 241 * Returns a description suitable for display or null. 242 * 243 * @return A description or null. 244 */ 245 @Nullable getDescription()246 public CharSequence getDescription() { 247 return mDescription; 248 } 249 250 /** 251 * Returns a bitmap icon suitable for display or null. 252 * 253 * @return An icon or null. 254 */ 255 @Nullable getIconBitmap()256 public Bitmap getIconBitmap() { 257 return mIcon; 258 } 259 260 /** 261 * Returns a Uri for an icon suitable for display or null. 262 * 263 * @return An icon uri or null. 264 */ 265 @Nullable getIconUri()266 public Uri getIconUri() { 267 return mIconUri; 268 } 269 270 /** 271 * Returns any extras that were added to the description. 272 * 273 * @return A bundle of extras or null. 274 */ 275 @Nullable getExtras()276 public Bundle getExtras() { 277 return mExtras; 278 } 279 280 /** 281 * Returns a Uri representing this content or null. 282 * 283 * @return A media Uri or null. 284 */ 285 @Nullable getMediaUri()286 public Uri getMediaUri() { 287 return mMediaUri; 288 } 289 290 @Override describeContents()291 public int describeContents() { 292 return 0; 293 } 294 295 @Override writeToParcel(Parcel dest, int flags)296 public void writeToParcel(Parcel dest, int flags) { 297 if (Build.VERSION.SDK_INT < 21) { 298 dest.writeString(mMediaId); 299 TextUtils.writeToParcel(mTitle, dest, flags); 300 TextUtils.writeToParcel(mSubtitle, dest, flags); 301 TextUtils.writeToParcel(mDescription, dest, flags); 302 dest.writeParcelable(mIcon, flags); 303 dest.writeParcelable(mIconUri, flags); 304 dest.writeBundle(mExtras); 305 dest.writeParcelable(mMediaUri, flags); 306 } else { 307 MediaDescriptionCompatApi21.writeToParcel(getMediaDescription(), dest, flags); 308 } 309 } 310 311 @Override toString()312 public String toString() { 313 return mTitle + ", " + mSubtitle + ", " + mDescription; 314 } 315 316 /** 317 * Gets the underlying framework {@link android.media.MediaDescription} 318 * object. 319 * <p> 320 * This method is only supported on 321 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. 322 * </p> 323 * 324 * @return An equivalent {@link android.media.MediaDescription} object, or 325 * null if none. 326 */ getMediaDescription()327 public Object getMediaDescription() { 328 if (mDescriptionObj != null || Build.VERSION.SDK_INT < 21) { 329 return mDescriptionObj; 330 } 331 Object bob = MediaDescriptionCompatApi21.Builder.newInstance(); 332 MediaDescriptionCompatApi21.Builder.setMediaId(bob, mMediaId); 333 MediaDescriptionCompatApi21.Builder.setTitle(bob, mTitle); 334 MediaDescriptionCompatApi21.Builder.setSubtitle(bob, mSubtitle); 335 MediaDescriptionCompatApi21.Builder.setDescription(bob, mDescription); 336 MediaDescriptionCompatApi21.Builder.setIconBitmap(bob, mIcon); 337 MediaDescriptionCompatApi21.Builder.setIconUri(bob, mIconUri); 338 // Media URI was not added until API 23, so add it to the Bundle of extras to 339 // ensure the data is not lost - this ensures that 340 // fromMediaDescription(getMediaDescription(mediaDescriptionCompat)) returns 341 // an equivalent MediaDescriptionCompat on all API levels 342 Bundle extras = mExtras; 343 if (Build.VERSION.SDK_INT < 23 && mMediaUri != null) { 344 if (extras == null) { 345 extras = new Bundle(); 346 extras.putBoolean(DESCRIPTION_KEY_NULL_BUNDLE_FLAG, true); 347 } 348 extras.putParcelable(DESCRIPTION_KEY_MEDIA_URI, mMediaUri); 349 } 350 MediaDescriptionCompatApi21.Builder.setExtras(bob, extras); 351 if (Build.VERSION.SDK_INT >= 23) { 352 MediaDescriptionCompatApi23.Builder.setMediaUri(bob, mMediaUri); 353 } 354 mDescriptionObj = MediaDescriptionCompatApi21.Builder.build(bob); 355 356 return mDescriptionObj; 357 } 358 359 /** 360 * Creates an instance from a framework 361 * {@link android.media.MediaDescription} object. 362 * <p> 363 * This method is only supported on API 21+. 364 * </p> 365 * 366 * @param descriptionObj A {@link android.media.MediaDescription} object, or 367 * null if none. 368 * @return An equivalent {@link MediaMetadataCompat} object, or null if 369 * none. 370 */ fromMediaDescription(Object descriptionObj)371 public static MediaDescriptionCompat fromMediaDescription(Object descriptionObj) { 372 if (descriptionObj != null && Build.VERSION.SDK_INT >= 21) { 373 Builder bob = new Builder(); 374 bob.setMediaId(MediaDescriptionCompatApi21.getMediaId(descriptionObj)); 375 bob.setTitle(MediaDescriptionCompatApi21.getTitle(descriptionObj)); 376 bob.setSubtitle(MediaDescriptionCompatApi21.getSubtitle(descriptionObj)); 377 bob.setDescription(MediaDescriptionCompatApi21.getDescription(descriptionObj)); 378 bob.setIconBitmap(MediaDescriptionCompatApi21.getIconBitmap(descriptionObj)); 379 bob.setIconUri(MediaDescriptionCompatApi21.getIconUri(descriptionObj)); 380 Bundle extras = MediaDescriptionCompatApi21.getExtras(descriptionObj); 381 Uri mediaUri = extras == null ? null : 382 (Uri) extras.getParcelable(DESCRIPTION_KEY_MEDIA_URI); 383 if (mediaUri != null) { 384 if (extras.containsKey(DESCRIPTION_KEY_NULL_BUNDLE_FLAG) && extras.size() == 2) { 385 // The extras were only created for the media URI, so we set it back to null to 386 // ensure mediaDescriptionCompat.getExtras() equals 387 // fromMediaDescription(getMediaDescription(mediaDescriptionCompat)).getExtras() 388 extras = null; 389 } else { 390 // Remove media URI keys to ensure mediaDescriptionCompat.getExtras().keySet() 391 // equals fromMediaDescription(getMediaDescription(mediaDescriptionCompat)) 392 // .getExtras().keySet() 393 extras.remove(DESCRIPTION_KEY_MEDIA_URI); 394 extras.remove(DESCRIPTION_KEY_NULL_BUNDLE_FLAG); 395 } 396 } 397 bob.setExtras(extras); 398 if (mediaUri != null) { 399 bob.setMediaUri(mediaUri); 400 } else if (Build.VERSION.SDK_INT >= 23) { 401 bob.setMediaUri(MediaDescriptionCompatApi23.getMediaUri(descriptionObj)); 402 } 403 MediaDescriptionCompat descriptionCompat = bob.build(); 404 descriptionCompat.mDescriptionObj = descriptionObj; 405 406 return descriptionCompat; 407 } else { 408 return null; 409 } 410 } 411 412 public static final Parcelable.Creator<MediaDescriptionCompat> CREATOR = 413 new Parcelable.Creator<MediaDescriptionCompat>() { 414 @Override 415 public MediaDescriptionCompat createFromParcel(Parcel in) { 416 if (Build.VERSION.SDK_INT < 21) { 417 return new MediaDescriptionCompat(in); 418 } else { 419 return fromMediaDescription(MediaDescriptionCompatApi21.fromParcel(in)); 420 } 421 } 422 423 @Override 424 public MediaDescriptionCompat[] newArray(int size) { 425 return new MediaDescriptionCompat[size]; 426 } 427 }; 428 429 /** 430 * Builder for {@link MediaDescriptionCompat} objects. 431 */ 432 public static final class Builder { 433 private String mMediaId; 434 private CharSequence mTitle; 435 private CharSequence mSubtitle; 436 private CharSequence mDescription; 437 private Bitmap mIcon; 438 private Uri mIconUri; 439 private Bundle mExtras; 440 private Uri mMediaUri; 441 442 /** 443 * Creates an initially empty builder. 444 */ Builder()445 public Builder() { 446 } 447 448 /** 449 * Sets the media id. 450 * 451 * @param mediaId The unique id for the item or null. 452 * @return this 453 */ setMediaId(@ullable String mediaId)454 public Builder setMediaId(@Nullable String mediaId) { 455 mMediaId = mediaId; 456 return this; 457 } 458 459 /** 460 * Sets the title. 461 * 462 * @param title A title suitable for display to the user or null. 463 * @return this 464 */ setTitle(@ullable CharSequence title)465 public Builder setTitle(@Nullable CharSequence title) { 466 mTitle = title; 467 return this; 468 } 469 470 /** 471 * Sets the subtitle. 472 * 473 * @param subtitle A subtitle suitable for display to the user or null. 474 * @return this 475 */ setSubtitle(@ullable CharSequence subtitle)476 public Builder setSubtitle(@Nullable CharSequence subtitle) { 477 mSubtitle = subtitle; 478 return this; 479 } 480 481 /** 482 * Sets the description. 483 * 484 * @param description A description suitable for display to the user or 485 * null. 486 * @return this 487 */ setDescription(@ullable CharSequence description)488 public Builder setDescription(@Nullable CharSequence description) { 489 mDescription = description; 490 return this; 491 } 492 493 /** 494 * Sets the icon. 495 * 496 * @param icon A {@link Bitmap} icon suitable for display to the user or 497 * null. 498 * @return this 499 */ setIconBitmap(@ullable Bitmap icon)500 public Builder setIconBitmap(@Nullable Bitmap icon) { 501 mIcon = icon; 502 return this; 503 } 504 505 /** 506 * Sets the icon uri. 507 * 508 * @param iconUri A {@link Uri} for an icon suitable for display to the 509 * user or null. 510 * @return this 511 */ setIconUri(@ullable Uri iconUri)512 public Builder setIconUri(@Nullable Uri iconUri) { 513 mIconUri = iconUri; 514 return this; 515 } 516 517 /** 518 * Sets a bundle of extras. 519 * 520 * @param extras The extras to include with this description or null. 521 * @return this 522 */ setExtras(@ullable Bundle extras)523 public Builder setExtras(@Nullable Bundle extras) { 524 mExtras = extras; 525 return this; 526 } 527 528 /** 529 * Sets the media uri. 530 * 531 * @param mediaUri The content's {@link Uri} for the item or null. 532 * @return this 533 */ setMediaUri(@ullable Uri mediaUri)534 public Builder setMediaUri(@Nullable Uri mediaUri) { 535 mMediaUri = mediaUri; 536 return this; 537 } 538 539 /** 540 * Creates a {@link MediaDescriptionCompat} instance with the specified 541 * fields. 542 * 543 * @return A MediaDescriptionCompat instance. 544 */ build()545 public MediaDescriptionCompat build() { 546 return new MediaDescriptionCompat(mMediaId, mTitle, mSubtitle, mDescription, mIcon, 547 mIconUri, mExtras, mMediaUri); 548 } 549 } 550 } 551