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.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.os.Bundle; 22 import android.text.TextUtils; 23 24 import androidx.annotation.IntDef; 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 27 import androidx.annotation.RestrictTo; 28 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.util.UUID; 32 33 /** 34 * A class with information on a single media item with the metadata information. 35 * Media item are application dependent so we cannot guarantee that they contain the right values. 36 * <p> 37 * When it's sent to a controller or browser, it's anonymized and data descriptor wouldn't be sent. 38 * <p> 39 * This object isn't a thread safe. 40 */ 41 public class MediaItem2 { 42 /** @hide */ 43 @RestrictTo(LIBRARY_GROUP) 44 @Retention(RetentionPolicy.SOURCE) 45 @IntDef(flag = true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE }) 46 public @interface Flags { } 47 48 /** 49 * Flag: Indicates that the item has children of its own. 50 */ 51 public static final int FLAG_BROWSABLE = 1 << 0; 52 53 /** 54 * Flag: Indicates that the item is playable. 55 * <p> 56 * The id of this item may be passed to 57 * {@link MediaController2#playFromMediaId(String, Bundle)} 58 */ 59 public static final int FLAG_PLAYABLE = 1 << 1; 60 61 private static final String KEY_ID = "android.media.mediaitem2.id"; 62 private static final String KEY_FLAGS = "android.media.mediaitem2.flags"; 63 private static final String KEY_METADATA = "android.media.mediaitem2.metadata"; 64 private static final String KEY_UUID = "android.media.mediaitem2.uuid"; 65 66 private final String mId; 67 private final int mFlags; 68 private final UUID mUUID; 69 private MediaMetadata2 mMetadata; 70 private DataSourceDesc mDataSourceDesc; 71 72 private MediaItem2(@NonNull String mediaId, @Nullable DataSourceDesc dsd, 73 @Nullable MediaMetadata2 metadata, @Flags int flags) { 74 this(mediaId, dsd, metadata, flags, null); 75 } 76 77 private MediaItem2(@NonNull String mediaId, @Nullable DataSourceDesc dsd, 78 @Nullable MediaMetadata2 metadata, @Flags int flags, @Nullable UUID uuid) { 79 if (mediaId == null) { 80 throw new IllegalArgumentException("mediaId shouldn't be null"); 81 } 82 if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) { 83 throw new IllegalArgumentException("metadata's id should be matched with the mediaid"); 84 } 85 86 mId = mediaId; 87 mDataSourceDesc = dsd; 88 mMetadata = metadata; 89 mFlags = flags; 90 mUUID = (uuid == null) ? UUID.randomUUID() : uuid; 91 } 92 /** 93 * Return this object as a bundle to share between processes. 94 * 95 * @return a new bundle instance 96 */ 97 public Bundle toBundle() { 98 Bundle bundle = new Bundle(); 99 bundle.putString(KEY_ID, mId); 100 bundle.putInt(KEY_FLAGS, mFlags); 101 if (mMetadata != null) { 102 bundle.putBundle(KEY_METADATA, mMetadata.toBundle()); 103 } 104 bundle.putString(KEY_UUID, mUUID.toString()); 105 return bundle; 106 } 107 108 /** 109 * Create a MediaItem2 from the {@link Bundle}. 110 * 111 * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}. 112 * @return The newly created MediaItem2 113 */ 114 public static MediaItem2 fromBundle(Bundle bundle) { 115 if (bundle == null) { 116 return null; 117 } 118 final String uuidString = bundle.getString(KEY_UUID); 119 return fromBundle(bundle, UUID.fromString(uuidString)); 120 } 121 122 /** 123 * Create a MediaItem2 from the {@link Bundle} with the specified {@link UUID}. 124 * If {@link UUID} 125 * can be null for creating new. 126 * 127 * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}. 128 * @param uuid A {@link UUID} to override. Can be {@link null} for override. 129 * @return The newly created MediaItem2 130 */ 131 static MediaItem2 fromBundle(@NonNull Bundle bundle, @Nullable UUID uuid) { 132 if (bundle == null) { 133 return null; 134 } 135 final String id = bundle.getString(KEY_ID); 136 final Bundle metadataBundle = bundle.getBundle(KEY_METADATA); 137 final MediaMetadata2 metadata = metadataBundle != null 138 ? MediaMetadata2.fromBundle(metadataBundle) : null; 139 final int flags = bundle.getInt(KEY_FLAGS); 140 return new MediaItem2(id, null, metadata, flags, uuid); 141 } 142 143 @Override 144 public String toString() { 145 final StringBuilder sb = new StringBuilder("MediaItem2{"); 146 sb.append("mFlags=").append(mFlags); 147 sb.append(", mMetadata=").append(mMetadata); 148 sb.append('}'); 149 return sb.toString(); 150 } 151 152 /** 153 * Gets the flags of the item. 154 */ 155 public @Flags int getFlags() { 156 return mFlags; 157 } 158 159 /** 160 * Returns whether this item is browsable. 161 * @see #FLAG_BROWSABLE 162 */ 163 public boolean isBrowsable() { 164 return (mFlags & FLAG_BROWSABLE) != 0; 165 } 166 167 /** 168 * Returns whether this item is playable. 169 * @see #FLAG_PLAYABLE 170 */ 171 public boolean isPlayable() { 172 return (mFlags & FLAG_PLAYABLE) != 0; 173 } 174 175 /** 176 * Set a metadata. If the metadata is not null, its id should be matched with this instance's 177 * media id. 178 * 179 * @param metadata metadata to update 180 */ 181 public void setMetadata(@Nullable MediaMetadata2 metadata) { 182 if (metadata != null && !TextUtils.equals(mId, metadata.getMediaId())) { 183 throw new IllegalArgumentException("metadata's id should be matched with the mediaId"); 184 } 185 mMetadata = metadata; 186 } 187 188 /** 189 * Returns the metadata of the media. 190 */ 191 public @Nullable MediaMetadata2 getMetadata() { 192 return mMetadata; 193 } 194 195 /** 196 * Returns the media id for this item. 197 */ 198 public /*@NonNull*/ String getMediaId() { 199 return mId; 200 } 201 202 /** 203 * Return the {@link DataSourceDesc} 204 * <p> 205 * Can be {@code null} if the MediaItem2 came from another process and anonymized 206 * 207 * @return data source descriptor 208 */ 209 public @Nullable DataSourceDesc getDataSourceDesc() { 210 return mDataSourceDesc; 211 } 212 213 @Override 214 public int hashCode() { 215 return mUUID.hashCode(); 216 } 217 218 @Override 219 public boolean equals(Object obj) { 220 if (!(obj instanceof MediaItem2)) { 221 return false; 222 } 223 MediaItem2 other = (MediaItem2) obj; 224 return mUUID.equals(other.mUUID); 225 } 226 227 /** 228 * Build {@link MediaItem2} 229 */ 230 public static final class Builder { 231 private @Flags int mFlags; 232 private String mMediaId; 233 private MediaMetadata2 mMetadata; 234 private DataSourceDesc mDataSourceDesc; 235 236 /** 237 * Constructor for {@link Builder} 238 * 239 * @param flags 240 */ 241 public Builder(@Flags int flags) { 242 mFlags = flags; 243 } 244 245 /** 246 * Set the media id of this instance. {@code null} for unset. 247 * <p> 248 * Media id is used to identify a media contents between session and controller. 249 * <p> 250 * If the metadata is set with the {@link #setMetadata(MediaMetadata2)} and it has 251 * media id, id from {@link #setMediaId(String)} will be ignored and metadata's id will be 252 * used instead. If the id isn't set neither by {@link #setMediaId(String)} nor 253 * {@link #setMetadata(MediaMetadata2)}, id will be automatically generated. 254 * 255 * @param mediaId media id 256 * @return this instance for chaining 257 */ 258 public Builder setMediaId(@Nullable String mediaId) { 259 mMediaId = mediaId; 260 return this; 261 } 262 263 /** 264 * Set the metadata of this instance. {@code null} for unset. 265 * <p> 266 * If the metadata is set with the {@link #setMetadata(MediaMetadata2)} and it has 267 * media id, id from {@link #setMediaId(String)} will be ignored and metadata's id will be 268 * used instead. If the id isn't set neither by {@link #setMediaId(String)} nor 269 * {@link #setMetadata(MediaMetadata2)}, id will be automatically generated. 270 * 271 * @param metadata metadata 272 * @return this instance for chaining 273 */ 274 public Builder setMetadata(@Nullable MediaMetadata2 metadata) { 275 mMetadata = metadata; 276 return this; 277 } 278 279 /** 280 * Set the data source descriptor for this instance. {@code null} for unset. 281 * 282 * @param dataSourceDesc data source descriptor 283 * @return this instance for chaining 284 */ 285 public Builder setDataSourceDesc(@Nullable DataSourceDesc dataSourceDesc) { 286 mDataSourceDesc = dataSourceDesc; 287 return this; 288 } 289 290 /** 291 * Build {@link MediaItem2}. 292 * 293 * @return a new {@link MediaItem2}. 294 */ 295 public MediaItem2 build() { 296 String id = (mMetadata != null) 297 ? mMetadata.getString(MediaMetadata2.METADATA_KEY_MEDIA_ID) : null; 298 if (id == null) { 299 id = (mMediaId != null) ? mMediaId : toString(); 300 } 301 return new MediaItem2(id, mDataSourceDesc, mMetadata, mFlags); 302 } 303 } 304 } 305