1 /* 2 * Copyright 2018 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 androidx.media; 18 19 import static androidx.media.MediaMetadata2.METADATA_KEY_DISPLAY_DESCRIPTION; 20 import static androidx.media.MediaMetadata2.METADATA_KEY_DISPLAY_ICON; 21 import static androidx.media.MediaMetadata2.METADATA_KEY_DISPLAY_ICON_URI; 22 import static androidx.media.MediaMetadata2.METADATA_KEY_DISPLAY_SUBTITLE; 23 import static androidx.media.MediaMetadata2.METADATA_KEY_DISPLAY_TITLE; 24 import static androidx.media.MediaMetadata2.METADATA_KEY_EXTRAS; 25 import static androidx.media.MediaMetadata2.METADATA_KEY_MEDIA_ID; 26 import static androidx.media.MediaMetadata2.METADATA_KEY_MEDIA_URI; 27 import static androidx.media.MediaMetadata2.METADATA_KEY_TITLE; 28 29 import android.graphics.Bitmap; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.os.Parcelable; 33 import android.support.v4.media.MediaBrowserCompat.MediaItem; 34 import android.support.v4.media.MediaDescriptionCompat; 35 import android.support.v4.media.MediaMetadataCompat; 36 import android.support.v4.media.RatingCompat; 37 import android.support.v4.media.session.PlaybackStateCompat; 38 39 import androidx.media.MediaSession2.CommandButton; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 44 class MediaUtils2 { 45 static final String TAG = "MediaUtils2"; 46 47 private MediaUtils2() { 48 } 49 50 /** 51 * Creates a {@link MediaItem} from the {@link MediaItem2}. 52 * 53 * @param item2 an item. 54 * @return The newly created media item. 55 */ 56 static MediaItem createMediaItem(MediaItem2 item2) { 57 if (item2 == null) { 58 return null; 59 } 60 MediaDescriptionCompat descCompat; 61 62 MediaMetadata2 metadata = item2.getMetadata(); 63 if (metadata == null) { 64 descCompat = new MediaDescriptionCompat.Builder() 65 .setMediaId(item2.getMediaId()) 66 .build(); 67 } else { 68 MediaDescriptionCompat.Builder builder = new MediaDescriptionCompat.Builder() 69 .setMediaId(item2.getMediaId()) 70 .setSubtitle(metadata.getText(METADATA_KEY_DISPLAY_SUBTITLE)) 71 .setDescription(metadata.getText(METADATA_KEY_DISPLAY_DESCRIPTION)) 72 .setIconBitmap(metadata.getBitmap(METADATA_KEY_DISPLAY_ICON)) 73 .setExtras(metadata.getExtras()); 74 75 String title = metadata.getString(METADATA_KEY_TITLE); 76 if (title != null) { 77 builder.setTitle(title); 78 } else { 79 builder.setTitle(metadata.getString(METADATA_KEY_DISPLAY_TITLE)); 80 } 81 82 String displayIconUri = metadata.getString(METADATA_KEY_DISPLAY_ICON_URI); 83 if (displayIconUri != null) { 84 builder.setIconUri(Uri.parse(displayIconUri)); 85 } 86 87 String mediaUri = metadata.getString(METADATA_KEY_MEDIA_URI); 88 if (mediaUri != null) { 89 builder.setMediaUri(Uri.parse(mediaUri)); 90 } 91 92 descCompat = builder.build(); 93 } 94 return new MediaItem(descCompat, item2.getFlags()); 95 } 96 97 /** 98 * Creates a {@link MediaItem2} from the {@link MediaItem}. 99 * 100 * @param item an item. 101 * @return The newly created media item. 102 */ 103 static MediaItem2 createMediaItem2(MediaItem item) { 104 if (item == null || item.getMediaId() == null) { 105 return null; 106 } 107 108 MediaMetadata2 metadata2 = createMediaMetadata2(item.getDescription()); 109 return new MediaItem2.Builder(item.getFlags()) 110 .setMediaId(item.getMediaId()) 111 .setMetadata(metadata2) 112 .build(); 113 } 114 115 static List<MediaItem> fromMediaItem2List(List<MediaItem2> items) { 116 if (items == null) { 117 return null; 118 } 119 List<MediaItem> result = new ArrayList<>(); 120 for (int i = 0; i < items.size(); i++) { 121 result.add(createMediaItem(items.get(i))); 122 } 123 return result; 124 } 125 126 static List<MediaItem2> toMediaItem2List(List<MediaItem> items) { 127 if (items == null) { 128 return null; 129 } 130 List<MediaItem2> result = new ArrayList<>(); 131 for (int i = 0; i < items.size(); i++) { 132 result.add(createMediaItem2(items.get(i))); 133 } 134 return result; 135 } 136 137 /** 138 * Creates a {@link MediaMetadata2} from the {@link MediaDescriptionCompat}. 139 * 140 * @param descCompat A {@link MediaDescriptionCompat} object. 141 * @return The newly created {@link MediaMetadata2} object. 142 */ 143 static MediaMetadata2 createMediaMetadata2(MediaDescriptionCompat descCompat) { 144 if (descCompat == null) { 145 return null; 146 } 147 148 MediaMetadata2.Builder metadata2Builder = new MediaMetadata2.Builder(); 149 metadata2Builder.putString(METADATA_KEY_MEDIA_ID, descCompat.getMediaId()); 150 151 CharSequence title = descCompat.getTitle(); 152 if (title != null) { 153 metadata2Builder.putText(METADATA_KEY_DISPLAY_TITLE, title); 154 } 155 156 CharSequence description = descCompat.getDescription(); 157 if (description != null) { 158 metadata2Builder.putText(METADATA_KEY_DISPLAY_DESCRIPTION, descCompat.getDescription()); 159 } 160 161 CharSequence subtitle = descCompat.getSubtitle(); 162 if (subtitle != null) { 163 metadata2Builder.putText(METADATA_KEY_DISPLAY_SUBTITLE, subtitle); 164 } 165 166 Bitmap icon = descCompat.getIconBitmap(); 167 if (icon != null) { 168 metadata2Builder.putBitmap(METADATA_KEY_DISPLAY_ICON, icon); 169 } 170 171 Uri iconUri = descCompat.getIconUri(); 172 if (iconUri != null) { 173 metadata2Builder.putText(METADATA_KEY_DISPLAY_ICON_URI, iconUri.toString()); 174 } 175 176 Bundle bundle = descCompat.getExtras(); 177 if (bundle != null) { 178 metadata2Builder.setExtras(descCompat.getExtras()); 179 } 180 181 Uri mediaUri = descCompat.getMediaUri(); 182 if (mediaUri != null) { 183 metadata2Builder.putText(METADATA_KEY_MEDIA_URI, mediaUri.toString()); 184 } 185 186 return metadata2Builder.build(); 187 } 188 189 /** 190 * Creates a {@link MediaMetadata2} from the {@link MediaMetadataCompat}. 191 * 192 * @param metadataCompat A {@link MediaMetadataCompat} object. 193 * @return The newly created {@link MediaMetadata2} object. 194 */ 195 MediaMetadata2 createMediaMetadata2(MediaMetadataCompat metadataCompat) { 196 if (metadataCompat == null) { 197 return null; 198 } 199 return new MediaMetadata2(metadataCompat.getBundle()); 200 } 201 202 /** 203 * Creates a {@link MediaMetadataCompat} from the {@link MediaMetadata2}. 204 * 205 * @param metadata2 A {@link MediaMetadata2} object. 206 * @return The newly created {@link MediaMetadataCompat} object. 207 */ 208 MediaMetadataCompat createMediaMetadataCompat(MediaMetadata2 metadata2) { 209 if (metadata2 == null) { 210 return null; 211 } 212 213 MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); 214 215 List<String> skippedKeys = new ArrayList<>(); 216 Bundle bundle = metadata2.toBundle(); 217 for (String key : bundle.keySet()) { 218 Object value = bundle.get(key); 219 if (value instanceof CharSequence) { 220 builder.putText(key, (CharSequence) value); 221 } else if (value instanceof Rating2) { 222 builder.putRating(key, createRatingCompat((Rating2) value)); 223 } else if (value instanceof Bitmap) { 224 builder.putBitmap(key, (Bitmap) value); 225 } else if (value instanceof Long) { 226 builder.putLong(key, (Long) value); 227 } else { 228 // There is no 'float' or 'bundle' type in MediaMetadataCompat. 229 skippedKeys.add(key); 230 } 231 } 232 233 MediaMetadataCompat result = builder.build(); 234 for (String key : skippedKeys) { 235 Object value = bundle.get(key); 236 if (value instanceof Float) { 237 // Compatibility for MediaMetadata2.Builder.putFloat() 238 result.getBundle().putFloat(key, (Float) value); 239 } else if (METADATA_KEY_EXTRAS.equals(value)) { 240 // Compatibility for MediaMetadata2.Builder.setExtras() 241 result.getBundle().putBundle(key, (Bundle) value); 242 } 243 } 244 return result; 245 } 246 247 /** 248 * Creates a {@link Rating2} from the {@link RatingCompat}. 249 * 250 * @param ratingCompat A {@link RatingCompat} object. 251 * @return The newly created {@link Rating2} object. 252 */ 253 Rating2 createRating2(RatingCompat ratingCompat) { 254 if (ratingCompat == null) { 255 return null; 256 } 257 if (!ratingCompat.isRated()) { 258 return Rating2.newUnratedRating(ratingCompat.getRatingStyle()); 259 } 260 261 switch (ratingCompat.getRatingStyle()) { 262 case RatingCompat.RATING_3_STARS: 263 case RatingCompat.RATING_4_STARS: 264 case RatingCompat.RATING_5_STARS: 265 return Rating2.newStarRating( 266 ratingCompat.getRatingStyle(), ratingCompat.getStarRating()); 267 case RatingCompat.RATING_HEART: 268 return Rating2.newHeartRating(ratingCompat.hasHeart()); 269 case RatingCompat.RATING_THUMB_UP_DOWN: 270 return Rating2.newThumbRating(ratingCompat.isThumbUp()); 271 case RatingCompat.RATING_PERCENTAGE: 272 return Rating2.newPercentageRating(ratingCompat.getPercentRating()); 273 default: 274 return null; 275 } 276 } 277 278 /** 279 * Creates a {@link RatingCompat} from the {@link Rating2}. 280 * 281 * @param rating2 A {@link Rating2} object. 282 * @return The newly created {@link RatingCompat} object. 283 */ 284 RatingCompat createRatingCompat(Rating2 rating2) { 285 if (rating2 == null) { 286 return null; 287 } 288 if (!rating2.isRated()) { 289 return RatingCompat.newUnratedRating(rating2.getRatingStyle()); 290 } 291 292 switch (rating2.getRatingStyle()) { 293 case Rating2.RATING_3_STARS: 294 case Rating2.RATING_4_STARS: 295 case Rating2.RATING_5_STARS: 296 return RatingCompat.newStarRating( 297 rating2.getRatingStyle(), rating2.getStarRating()); 298 case Rating2.RATING_HEART: 299 return RatingCompat.newHeartRating(rating2.hasHeart()); 300 case Rating2.RATING_THUMB_UP_DOWN: 301 return RatingCompat.newThumbRating(rating2.isThumbUp()); 302 case Rating2.RATING_PERCENTAGE: 303 return RatingCompat.newPercentageRating(rating2.getPercentRating()); 304 default: 305 return null; 306 } 307 } 308 309 static Parcelable[] toMediaItem2ParcelableArray(List<MediaItem2> playlist) { 310 if (playlist == null) { 311 return null; 312 } 313 List<Parcelable> parcelableList = new ArrayList<>(); 314 for (int i = 0; i < playlist.size(); i++) { 315 final MediaItem2 item = playlist.get(i); 316 if (item != null) { 317 final Parcelable itemBundle = item.toBundle(); 318 if (itemBundle != null) { 319 parcelableList.add(itemBundle); 320 } 321 } 322 } 323 return parcelableList.toArray(new Parcelable[0]); 324 } 325 326 static List<MediaItem2> fromMediaItem2ParcelableArray(Parcelable[] itemParcelableList) { 327 List<MediaItem2> playlist = new ArrayList<>(); 328 if (itemParcelableList != null) { 329 for (int i = 0; i < itemParcelableList.length; i++) { 330 if (!(itemParcelableList[i] instanceof Bundle)) { 331 continue; 332 } 333 MediaItem2 item = MediaItem2.fromBundle((Bundle) itemParcelableList[i]); 334 if (item != null) { 335 playlist.add(item); 336 } 337 } 338 } 339 return playlist; 340 } 341 342 static Parcelable[] toCommandButtonParcelableArray(List<CommandButton> layout) { 343 if (layout == null) { 344 return null; 345 } 346 List<Bundle> layoutBundles = new ArrayList<>(); 347 for (int i = 0; i < layout.size(); i++) { 348 Bundle bundle = layout.get(i).toBundle(); 349 if (bundle != null) { 350 layoutBundles.add(bundle); 351 } 352 } 353 return layoutBundles.toArray(new Parcelable[0]); 354 } 355 356 static List<CommandButton> fromCommandButtonParcelableArray(Parcelable[] list) { 357 List<CommandButton> layout = new ArrayList<>(); 358 if (layout != null) { 359 for (int i = 0; i < list.length; i++) { 360 if (!(list[i] instanceof Bundle)) { 361 continue; 362 } 363 CommandButton button = CommandButton.fromBundle((Bundle) list[i]); 364 if (button != null) { 365 layout.add(button); 366 } 367 } 368 } 369 return layout; 370 } 371 372 static List<Bundle> toBundleList(Parcelable[] array) { 373 if (array == null) { 374 return null; 375 } 376 List<Bundle> bundleList = new ArrayList<>(); 377 for (Parcelable p : array) { 378 bundleList.add((Bundle) p); 379 } 380 return bundleList; 381 } 382 383 static int createPlaybackStateCompatState(int playerState, int bufferingState) { 384 switch (playerState) { 385 case MediaPlayerInterface.PLAYER_STATE_PLAYING: 386 switch (bufferingState) { 387 case MediaPlayerInterface.BUFFERING_STATE_BUFFERING_AND_STARVED: 388 return PlaybackStateCompat.STATE_BUFFERING; 389 } 390 return PlaybackStateCompat.STATE_PLAYING; 391 case MediaPlayerInterface.PLAYER_STATE_PAUSED: 392 return PlaybackStateCompat.STATE_PAUSED; 393 case MediaPlayerInterface.PLAYER_STATE_IDLE: 394 return PlaybackStateCompat.STATE_NONE; 395 case MediaPlayerInterface.PLAYER_STATE_ERROR: 396 return PlaybackStateCompat.STATE_ERROR; 397 } 398 // For unknown value 399 return PlaybackStateCompat.STATE_ERROR; 400 } 401 402 static int toPlayerState(int playbackStateCompatState) { 403 switch (playbackStateCompatState) { 404 case PlaybackStateCompat.STATE_ERROR: 405 return MediaPlayerInterface.PLAYER_STATE_ERROR; 406 case PlaybackStateCompat.STATE_NONE: 407 return MediaPlayerInterface.PLAYER_STATE_IDLE; 408 case PlaybackStateCompat.STATE_PAUSED: 409 case PlaybackStateCompat.STATE_STOPPED: 410 case PlaybackStateCompat.STATE_BUFFERING: // means paused for buffering. 411 return MediaPlayerInterface.PLAYER_STATE_PAUSED; 412 case PlaybackStateCompat.STATE_FAST_FORWARDING: 413 case PlaybackStateCompat.STATE_PLAYING: 414 case PlaybackStateCompat.STATE_REWINDING: 415 case PlaybackStateCompat.STATE_SKIPPING_TO_NEXT: 416 case PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS: 417 case PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM: 418 case PlaybackStateCompat.STATE_CONNECTING: // Note: there's no perfect match for this. 419 return MediaPlayerInterface.PLAYER_STATE_PLAYING; 420 } 421 return MediaPlayerInterface.PLAYER_STATE_ERROR; 422 } 423 424 static boolean isDefaultLibraryRootHint(Bundle bundle) { 425 return bundle != null && bundle.getBoolean(MediaConstants2.ROOT_EXTRA_DEFAULT, false); 426 } 427 428 static Bundle createBundle(Bundle bundle) { 429 return (bundle == null) ? new Bundle() : new Bundle(bundle); 430 } 431 } 432