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