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