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.media;
17 
18 import android.annotation.IntRange;
19 import android.annotation.NonNull;
20 import android.annotation.StringDef;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.ContentResolver;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.media.browse.MediaBrowser;
26 import android.media.session.MediaController;
27 import android.media.session.MediaSession;
28 import android.net.Uri;
29 import android.os.Build;
30 import android.os.Bundle;
31 import android.os.Parcel;
32 import android.os.Parcelable;
33 import android.text.TextUtils;
34 import android.util.ArrayMap;
35 import android.util.Log;
36 import android.util.SparseArray;
37 
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.util.Objects;
41 import java.util.Set;
42 
43 /**
44  * Contains metadata about an item, such as the title, artist, etc.
45  */
46 public final class MediaMetadata implements Parcelable {
47     private static final String TAG = "MediaMetadata";
48 
49     /**
50      * @hide
51      */
52     @StringDef(prefix = { "METADATA_KEY_" }, value = {
53             METADATA_KEY_TITLE,
54             METADATA_KEY_ARTIST,
55             METADATA_KEY_ALBUM,
56             METADATA_KEY_AUTHOR,
57             METADATA_KEY_WRITER,
58             METADATA_KEY_COMPOSER,
59             METADATA_KEY_COMPILATION,
60             METADATA_KEY_DATE,
61             METADATA_KEY_GENRE,
62             METADATA_KEY_ALBUM_ARTIST,
63             METADATA_KEY_ART_URI,
64             METADATA_KEY_ALBUM_ART_URI,
65             METADATA_KEY_DISPLAY_TITLE,
66             METADATA_KEY_DISPLAY_SUBTITLE,
67             METADATA_KEY_DISPLAY_DESCRIPTION,
68             METADATA_KEY_DISPLAY_ICON_URI,
69             METADATA_KEY_MEDIA_ID,
70             METADATA_KEY_MEDIA_URI,
71     })
72     @Retention(RetentionPolicy.SOURCE)
73     public @interface TextKey {}
74 
75     /**
76      * @hide
77      */
78     @StringDef(prefix = { "METADATA_KEY_" }, value = {
79             METADATA_KEY_DURATION,
80             METADATA_KEY_YEAR,
81             METADATA_KEY_TRACK_NUMBER,
82             METADATA_KEY_NUM_TRACKS,
83             METADATA_KEY_DISC_NUMBER,
84             METADATA_KEY_BT_FOLDER_TYPE,
85     })
86     @Retention(RetentionPolicy.SOURCE)
87     public @interface LongKey {}
88 
89     /**
90      * @hide
91      */
92     @StringDef(prefix = { "METADATA_KEY_" }, value = {
93             METADATA_KEY_ART,
94             METADATA_KEY_ALBUM_ART,
95             METADATA_KEY_DISPLAY_ICON,
96     })
97     @Retention(RetentionPolicy.SOURCE)
98     public @interface BitmapKey {}
99 
100     /**
101      * @hide
102      */
103     @StringDef(prefix = { "METADATA_KEY_" }, value = {
104             METADATA_KEY_USER_RATING,
105             METADATA_KEY_RATING,
106     })
107     @Retention(RetentionPolicy.SOURCE)
108     public @interface RatingKey {}
109 
110     /**
111      * The title of the media.
112      */
113     public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
114 
115     /**
116      * The artist of the media.
117      */
118     public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
119 
120     /**
121      * The duration of the media in ms. A negative duration indicates that the
122      * duration is unknown (or infinite).
123      */
124     public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
125 
126     /**
127      * The album title for the media.
128      */
129     public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
130 
131     /**
132      * The author of the media.
133      */
134     public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
135 
136     /**
137      * The writer of the media.
138      */
139     public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
140 
141     /**
142      * The composer of the media.
143      */
144     public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
145 
146     /**
147      * The compilation status of the media.
148      */
149     public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
150 
151     /**
152      * The date the media was created or published. The format is unspecified
153      * but RFC 3339 is recommended.
154      */
155     public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
156 
157     /**
158      * The year the media was created or published as a long.
159      */
160     public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
161 
162     /**
163      * The genre of the media.
164      */
165     public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
166 
167     /**
168      * The track number for the media.
169      */
170     public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
171 
172     /**
173      * The number of tracks in the media's original source.
174      */
175     public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
176 
177     /**
178      * The disc number for the media's original source.
179      */
180     public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
181 
182     /**
183      * The artist for the album of the media's original source.
184      */
185     public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
186 
187     /**
188      * The artwork for the media as a {@link Bitmap}.
189      * <p>
190      * The artwork should be relatively small and may be scaled down by the
191      * system if it is too large. For higher resolution artwork
192      * {@link #METADATA_KEY_ART_URI} should be used instead.
193      */
194     public static final String METADATA_KEY_ART = "android.media.metadata.ART";
195 
196     /**
197      * The artwork for the media as a Uri formatted String. The artwork can be
198      * loaded using a combination of {@link ContentResolver#openInputStream} and
199      * {@link BitmapFactory#decodeStream}.
200      * <p>
201      * For the best results, Uris should use the content:// style and support
202      * {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork through
203      * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}.
204      */
205     public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
206 
207     /**
208      * The artwork for the album of the media's original source as a
209      * {@link Bitmap}.
210      * <p>
211      * The artwork should be relatively small and may be scaled down by the
212      * system if it is too large. For higher resolution artwork
213      * {@link #METADATA_KEY_ALBUM_ART_URI} should be used instead.
214      */
215     public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
216 
217     /**
218      * The artwork for the album of the media's original source as a Uri
219      * formatted String. The artwork can be loaded using a combination of
220      * {@link ContentResolver#openInputStream} and
221      * {@link BitmapFactory#decodeStream}.
222      * <p>
223      * For the best results, Uris should use the content:// style and support
224      * {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork through
225      * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}.
226      */
227     public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
228 
229     /**
230      * The user's rating for the media.
231      *
232      * @see Rating
233      */
234     public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
235 
236     /**
237      * The overall rating for the media.
238      *
239      * @see Rating
240      */
241     public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
242 
243     /**
244      * A title that is suitable for display to the user. This will generally be
245      * the same as {@link #METADATA_KEY_TITLE} but may differ for some formats.
246      * When displaying media described by this metadata this should be preferred
247      * if present.
248      */
249     public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
250 
251     /**
252      * A subtitle that is suitable for display to the user. When displaying a
253      * second line for media described by this metadata this should be preferred
254      * to other fields if present.
255      */
256     public static final String METADATA_KEY_DISPLAY_SUBTITLE =
257             "android.media.metadata.DISPLAY_SUBTITLE";
258 
259     /**
260      * A description that is suitable for display to the user. When displaying
261      * more information for media described by this metadata this should be
262      * preferred to other fields if present.
263      */
264     public static final String METADATA_KEY_DISPLAY_DESCRIPTION =
265             "android.media.metadata.DISPLAY_DESCRIPTION";
266 
267     /**
268      * An icon or thumbnail that is suitable for display to the user. When
269      * displaying an icon for media described by this metadata this should be
270      * preferred to other fields if present. This must be a {@link Bitmap}.
271      * <p>
272      * The icon should be relatively small and may be scaled down by the system
273      * if it is too large. For higher resolution artwork
274      * {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead.
275      */
276     public static final String METADATA_KEY_DISPLAY_ICON =
277             "android.media.metadata.DISPLAY_ICON";
278 
279     /**
280      * A Uri formatted String for an icon or thumbnail that is suitable for
281      * display to the user. When displaying more information for media described
282      * by this metadata the display description should be preferred to other
283      * fields when present. The icon can be loaded using a combination of
284      * {@link ContentResolver#openInputStream} and
285      * {@link BitmapFactory#decodeStream}.
286      * <p>
287      * For the best results, Uris should use the content:// style and support
288      * {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork through
289      * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}.
290      */
291     public static final String METADATA_KEY_DISPLAY_ICON_URI =
292             "android.media.metadata.DISPLAY_ICON_URI";
293 
294     /**
295      * A String key for identifying the content. This value is specific to the
296      * service providing the content. If used, this should be a persistent
297      * unique key for the underlying content. It may be used with
298      * {@link MediaController.TransportControls#playFromMediaId(String, Bundle)}
299      * to initiate playback when provided by a {@link MediaBrowser} connected to
300      * the same app.
301      */
302     public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
303 
304     /**
305      * A Uri formatted String representing the content. This value is specific to the
306      * service providing the content. It may be used with
307      * {@link MediaController.TransportControls#playFromUri(Uri, Bundle)}
308      * to initiate playback when provided by a {@link MediaBrowser} connected to
309      * the same app.
310      */
311     public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
312 
313     /**
314      * The bluetooth folder type of the media specified in the section 6.10.2.2 of the Bluetooth
315      * AVRCP 1.5. It should be one of the following:
316      * <ul>
317      * <li>{@link MediaDescription#BT_FOLDER_TYPE_MIXED}</li>
318      * <li>{@link MediaDescription#BT_FOLDER_TYPE_TITLES}</li>
319      * <li>{@link MediaDescription#BT_FOLDER_TYPE_ALBUMS}</li>
320      * <li>{@link MediaDescription#BT_FOLDER_TYPE_ARTISTS}</li>
321      * <li>{@link MediaDescription#BT_FOLDER_TYPE_GENRES}</li>
322      * <li>{@link MediaDescription#BT_FOLDER_TYPE_PLAYLISTS}</li>
323      * <li>{@link MediaDescription#BT_FOLDER_TYPE_YEARS}</li>
324      * </ul>
325      */
326     public static final String METADATA_KEY_BT_FOLDER_TYPE =
327             "android.media.metadata.BT_FOLDER_TYPE";
328 
329     private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
330             METADATA_KEY_TITLE,
331             METADATA_KEY_ARTIST,
332             METADATA_KEY_ALBUM,
333             METADATA_KEY_ALBUM_ARTIST,
334             METADATA_KEY_WRITER,
335             METADATA_KEY_AUTHOR,
336             METADATA_KEY_COMPOSER
337     };
338 
339     private static final @BitmapKey String[] PREFERRED_BITMAP_ORDER = {
340             METADATA_KEY_DISPLAY_ICON,
341             METADATA_KEY_ART,
342             METADATA_KEY_ALBUM_ART
343     };
344 
345     private static final @TextKey String[] PREFERRED_URI_ORDER = {
346             METADATA_KEY_DISPLAY_ICON_URI,
347             METADATA_KEY_ART_URI,
348             METADATA_KEY_ALBUM_ART_URI
349     };
350 
351     private static final int METADATA_TYPE_INVALID = -1;
352     private static final int METADATA_TYPE_LONG = 0;
353     private static final int METADATA_TYPE_TEXT = 1;
354     private static final int METADATA_TYPE_BITMAP = 2;
355     private static final int METADATA_TYPE_RATING = 3;
356     private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
357 
358     static {
359         METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT)360         METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT)361         METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG)362         METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT)363         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT)364         METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT)365         METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT)366         METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT)367         METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT)368         METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG)369         METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT)370         METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG)371         METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG)372         METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG)373         METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT)374         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP)375         METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT)376         METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP)377         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT)378         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING)379         METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING)380         METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT)381         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT)382         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT)383         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP)384         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT)385         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG)386         METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT)387         METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT)388         METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
389     }
390 
391     private static final SparseArray<String> EDITOR_KEY_MAPPING;
392 
393     static {
394         EDITOR_KEY_MAPPING = new SparseArray<String>();
EDITOR_KEY_MAPPING.put(MediaMetadataEditor.BITMAP_KEY_ARTWORK, METADATA_KEY_ART)395         EDITOR_KEY_MAPPING.put(MediaMetadataEditor.BITMAP_KEY_ARTWORK, METADATA_KEY_ART);
EDITOR_KEY_MAPPING.put(MediaMetadataEditor.RATING_KEY_BY_OTHERS, METADATA_KEY_RATING)396         EDITOR_KEY_MAPPING.put(MediaMetadataEditor.RATING_KEY_BY_OTHERS, METADATA_KEY_RATING);
EDITOR_KEY_MAPPING.put(MediaMetadataEditor.RATING_KEY_BY_USER, METADATA_KEY_USER_RATING)397         EDITOR_KEY_MAPPING.put(MediaMetadataEditor.RATING_KEY_BY_USER, METADATA_KEY_USER_RATING);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ALBUM, METADATA_KEY_ALBUM)398         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ALBUM, METADATA_KEY_ALBUM);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, METADATA_KEY_ALBUM_ARTIST)399         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
400                 METADATA_KEY_ALBUM_ARTIST);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ARTIST, METADATA_KEY_ARTIST)401         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ARTIST, METADATA_KEY_ARTIST);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_AUTHOR, METADATA_KEY_AUTHOR)402         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_AUTHOR, METADATA_KEY_AUTHOR);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, METADATA_KEY_TRACK_NUMBER)403         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
404                 METADATA_KEY_TRACK_NUMBER);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_COMPOSER, METADATA_KEY_COMPOSER)405         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_COMPOSER, METADATA_KEY_COMPOSER);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_COMPILATION, METADATA_KEY_COMPILATION)406         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_COMPILATION,
407                 METADATA_KEY_COMPILATION);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DATE, METADATA_KEY_DATE)408         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DATE, METADATA_KEY_DATE);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, METADATA_KEY_DISC_NUMBER)409         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
410                 METADATA_KEY_DISC_NUMBER);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DURATION, METADATA_KEY_DURATION)411         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DURATION, METADATA_KEY_DURATION);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_GENRE, METADATA_KEY_GENRE)412         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_GENRE, METADATA_KEY_GENRE);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS, METADATA_KEY_NUM_TRACKS)413         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS,
414                 METADATA_KEY_NUM_TRACKS);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_TITLE, METADATA_KEY_TITLE)415         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_TITLE, METADATA_KEY_TITLE);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_WRITER, METADATA_KEY_WRITER)416         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_WRITER, METADATA_KEY_WRITER);
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_YEAR, METADATA_KEY_YEAR)417         EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_YEAR, METADATA_KEY_YEAR);
418     }
419 
420     private final Bundle mBundle;
421     private final int mBitmapDimensionLimit;
422     private MediaDescription mDescription;
423 
MediaMetadata(Bundle bundle, int bitmapDimensionLimit)424     private MediaMetadata(Bundle bundle, int bitmapDimensionLimit) {
425         mBundle = new Bundle(bundle);
426         mBitmapDimensionLimit = bitmapDimensionLimit;
427     }
428 
MediaMetadata(Parcel in)429     private MediaMetadata(Parcel in) {
430         mBundle = in.readBundle();
431         mBitmapDimensionLimit = Math.max(in.readInt(), 1);
432 
433         // Proactively read bitmaps from known bitmap keys, to ensure that they're unparceled and
434         // added to mBundle's internal map. This ensures that the GC accounts for the underlying
435         // allocations, which it does not do if the bitmaps remain parceled (see b/215820910).
436         // TODO(b/223225532): Remove this workaround once the underlying allocations are properly
437         // tracked in NativeAllocationsRegistry.
438         getBitmap(METADATA_KEY_ART);
439         getBitmap(METADATA_KEY_ALBUM_ART);
440         getBitmap(METADATA_KEY_DISPLAY_ICON);
441     }
442 
443     /**
444      * Returns true if the given key is contained in the metadata
445      *
446      * @param key a String key
447      * @return true if the key exists in this metadata, false otherwise
448      */
containsKey(String key)449     public boolean containsKey(String key) {
450         return mBundle.containsKey(key);
451     }
452 
453     /**
454      * Returns the value associated with the given key, or null if no mapping of
455      * the desired type exists for the given key or a null value is explicitly
456      * associated with the key.
457      *
458      * @param key The key the value is stored under
459      * @return a CharSequence value, or null
460      */
getText(@extKey String key)461     public CharSequence getText(@TextKey String key) {
462         return mBundle.getCharSequence(key);
463     }
464 
465     /**
466      * Returns the text value associated with the given key as a String, or null
467      * if no mapping of the desired type exists for the given key or a null
468      * value is explicitly associated with the key. This is equivalent to
469      * calling {@link #getText getText().toString()} if the value is not null.
470      *
471      * @param key The key the value is stored under
472      * @return a String value, or null
473      */
getString(@extKey String key)474     public String getString(@TextKey String key) {
475         CharSequence text = getText(key);
476         if (text != null) {
477             return text.toString();
478         }
479         return null;
480     }
481 
482     /**
483      * Returns the value associated with the given key, or 0L if no long exists
484      * for the given key.
485      *
486      * @param key The key the value is stored under
487      * @return a long value
488      */
getLong(@ongKey String key)489     public long getLong(@LongKey String key) {
490         return mBundle.getLong(key, 0);
491     }
492 
493     /**
494      * Returns a {@link Rating} for the given key or null if no rating exists
495      * for the given key.
496      *
497      * @param key The key the value is stored under
498      * @return A {@link Rating} or null
499      */
getRating(@atingKey String key)500     public Rating getRating(@RatingKey String key) {
501         Rating rating = null;
502         try {
503             rating = mBundle.getParcelable(key, android.media.Rating.class);
504         } catch (Exception e) {
505             // ignore, value was not a bitmap
506             Log.w(TAG, "Failed to retrieve a key as Rating.", e);
507         }
508         return rating;
509     }
510 
511     /**
512      * Returns a {@link Bitmap} for the given key or null if no bitmap exists
513      * for the given key.
514      *
515      * @param key The key the value is stored under
516      * @return A {@link Bitmap} or null
517      */
getBitmap(@itmapKey String key)518     public Bitmap getBitmap(@BitmapKey String key) {
519         Bitmap bmp = null;
520         try {
521             bmp = mBundle.getParcelable(key, android.graphics.Bitmap.class);
522         } catch (Exception e) {
523             // ignore, value was not a bitmap
524             Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
525         }
526         return bmp;
527     }
528 
529     /**
530      * Gets the width/height limit (in pixels) for the bitmaps when this metadata was created.
531      * This method always returns a positive value.
532      * <p>
533      * If it returns {@link Integer#MAX_VALUE}, then no scaling down was applied to the bitmaps
534      * when this metadata was created.
535      * <p>
536      * If it returns another positive value, then all the bitmaps in this metadata has width/height
537      * not greater than this limit. Bitmaps may have been scaled down according to the limit.
538      * <p>
539      *
540      * @see Builder#setBitmapDimensionLimit(int)
541      */
getBitmapDimensionLimit()542     public @IntRange(from = 1) int getBitmapDimensionLimit() {
543         return mBitmapDimensionLimit;
544     }
545 
546     @Override
describeContents()547     public int describeContents() {
548         return 0;
549     }
550 
551     @Override
writeToParcel(Parcel dest, int flags)552     public void writeToParcel(Parcel dest, int flags) {
553         dest.writeBundle(mBundle);
554         dest.writeInt(mBitmapDimensionLimit);
555     }
556 
557     /**
558      * Returns the number of fields in this metadata.
559      *
560      * @return The number of fields in the metadata.
561      */
size()562     public int size() {
563         return mBundle.size();
564     }
565 
566     /**
567      * Returns a Set containing the Strings used as keys in this metadata.
568      *
569      * @return a Set of String keys
570      */
keySet()571     public Set<String> keySet() {
572         return mBundle.keySet();
573     }
574 
575     /**
576      * Returns a simple description of this metadata for display purposes.
577      *
578      * @return A simple description of this metadata.
579      */
getDescription()580     public @NonNull MediaDescription getDescription() {
581         if (mDescription != null) {
582             return mDescription;
583         }
584 
585         String mediaId = getString(METADATA_KEY_MEDIA_ID);
586 
587         CharSequence[] text = new CharSequence[3];
588         Bitmap icon = null;
589         Uri iconUri = null;
590 
591         // First handle the case where display data is set already
592         CharSequence displayText = getText(METADATA_KEY_DISPLAY_TITLE);
593         if (!TextUtils.isEmpty(displayText)) {
594             // If they have a display title use only display data, otherwise use
595             // our best bets
596             text[0] = displayText;
597             text[1] = getText(METADATA_KEY_DISPLAY_SUBTITLE);
598             text[2] = getText(METADATA_KEY_DISPLAY_DESCRIPTION);
599         } else {
600             // Use whatever fields we can
601             int textIndex = 0;
602             int keyIndex = 0;
603             while (textIndex < text.length && keyIndex < PREFERRED_DESCRIPTION_ORDER.length) {
604                 CharSequence next = getText(PREFERRED_DESCRIPTION_ORDER[keyIndex++]);
605                 if (!TextUtils.isEmpty(next)) {
606                     // Fill in the next empty bit of text
607                     text[textIndex++] = next;
608                 }
609             }
610         }
611 
612         // Get the best art bitmap we can find
613         for (int i = 0; i < PREFERRED_BITMAP_ORDER.length; i++) {
614             Bitmap next = getBitmap(PREFERRED_BITMAP_ORDER[i]);
615             if (next != null) {
616                 icon = next;
617                 break;
618             }
619         }
620 
621         // Get the best Uri we can find
622         for (int i = 0; i < PREFERRED_URI_ORDER.length; i++) {
623             String next = getString(PREFERRED_URI_ORDER[i]);
624             if (!TextUtils.isEmpty(next)) {
625                 iconUri = Uri.parse(next);
626                 break;
627             }
628         }
629 
630         Uri mediaUri = null;
631         String mediaUriStr = getString(METADATA_KEY_MEDIA_URI);
632         if (!TextUtils.isEmpty(mediaUriStr)) {
633             mediaUri = Uri.parse(mediaUriStr);
634         }
635 
636         MediaDescription.Builder bob = new MediaDescription.Builder();
637         bob.setMediaId(mediaId);
638         bob.setTitle(text[0]);
639         bob.setSubtitle(text[1]);
640         bob.setDescription(text[2]);
641         bob.setIconBitmap(icon);
642         bob.setIconUri(iconUri);
643         bob.setMediaUri(mediaUri);
644         if (mBundle.containsKey(METADATA_KEY_BT_FOLDER_TYPE)) {
645             Bundle bundle = new Bundle();
646             bundle.putLong(MediaDescription.EXTRA_BT_FOLDER_TYPE,
647                     getLong(METADATA_KEY_BT_FOLDER_TYPE));
648             bob.setExtras(bundle);
649         }
650         mDescription = bob.build();
651 
652         return mDescription;
653     }
654 
655     /**
656      * Helper for getting the String key used by {@link MediaMetadata} from the
657      * integer key that {@link MediaMetadataEditor} uses.
658      *
659      * @param editorKey The key used by the editor
660      * @return The key used by this class or null if no mapping exists
661      * @hide
662      */
663     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getKeyFromMetadataEditorKey(int editorKey)664     public static String getKeyFromMetadataEditorKey(int editorKey) {
665         return EDITOR_KEY_MAPPING.get(editorKey, null);
666     }
667 
668     public static final @android.annotation.NonNull Parcelable.Creator<MediaMetadata> CREATOR =
669             new Parcelable.Creator<MediaMetadata>() {
670                 @Override
671                 public MediaMetadata createFromParcel(Parcel in) {
672                     return new MediaMetadata(in);
673                 }
674 
675                 @Override
676                 public MediaMetadata[] newArray(int size) {
677                     return new MediaMetadata[size];
678                 }
679             };
680 
681     /**
682      * Compares the contents of this object to another MediaMetadata object. It
683      * does not compare Bitmaps and Ratings as the media player can choose to
684      * forgo these fields depending on how you retrieve the MediaMetadata.
685      *
686      * @param o The Metadata object to compare this object against
687      * @return Whether or not the two objects have matching fields (excluding
688      * Bitmaps and Ratings)
689      */
690     @Override
equals(Object o)691     public boolean equals(Object o) {
692         if (o == this) {
693             return true;
694         }
695 
696         if (!(o instanceof MediaMetadata)) {
697             return false;
698         }
699 
700         final MediaMetadata m = (MediaMetadata) o;
701 
702         for (int i = 0; i < METADATA_KEYS_TYPE.size(); i++) {
703             String key = METADATA_KEYS_TYPE.keyAt(i);
704             switch (METADATA_KEYS_TYPE.valueAt(i)) {
705                 case METADATA_TYPE_TEXT:
706                     if (!Objects.equals(getString(key), m.getString(key))) {
707                         return false;
708                     }
709                     break;
710                 case METADATA_TYPE_LONG:
711                     if (getLong(key) != m.getLong(key)) {
712                         return false;
713                     }
714                     break;
715                 default:
716                     // Ignore ratings and bitmaps when comparing
717                     break;
718             }
719         }
720 
721         return true;
722     }
723 
724     @Override
hashCode()725     public int hashCode() {
726         int hashCode = 17;
727 
728         for (int i = 0; i < METADATA_KEYS_TYPE.size(); i++) {
729             String key = METADATA_KEYS_TYPE.keyAt(i);
730             switch (METADATA_KEYS_TYPE.valueAt(i)) {
731                 case METADATA_TYPE_TEXT:
732                     hashCode = 31 * hashCode + Objects.hash(getString(key));
733                     break;
734                 case METADATA_TYPE_LONG:
735                     hashCode = 31 * hashCode + Long.hashCode(getLong(key));
736                     break;
737                 default:
738                     // Ignore ratings and bitmaps when comparing
739                     break;
740             }
741         }
742 
743         return hashCode;
744     }
745 
746     /**
747      * Use to build MediaMetadata objects. The system defined metadata keys must
748      * use the appropriate data type.
749      */
750     public static final class Builder {
751         private final Bundle mBundle;
752         private int mBitmapDimensionLimit = Integer.MAX_VALUE;
753 
754         /**
755          * Create an empty Builder. Any field that should be included in the
756          * {@link MediaMetadata} must be added.
757          */
Builder()758         public Builder() {
759             mBundle = new Bundle();
760         }
761 
762         /**
763          * Create a Builder using a {@link MediaMetadata} instance to set the
764          * initial values. All fields in the source metadata will be included in
765          * the new metadata. Fields can be overwritten by adding the same key.
766          *
767          * @param source
768          */
Builder(MediaMetadata source)769         public Builder(MediaMetadata source) {
770             mBundle = new Bundle(source.mBundle);
771             mBitmapDimensionLimit = source.mBitmapDimensionLimit;
772         }
773 
774         /**
775          * Put a CharSequence value into the metadata. Custom keys may be used,
776          * but if the METADATA_KEYs defined in this class are used they may only
777          * be one of the following:
778          * <ul>
779          * <li>{@link #METADATA_KEY_TITLE}</li>
780          * <li>{@link #METADATA_KEY_ARTIST}</li>
781          * <li>{@link #METADATA_KEY_ALBUM}</li>
782          * <li>{@link #METADATA_KEY_AUTHOR}</li>
783          * <li>{@link #METADATA_KEY_WRITER}</li>
784          * <li>{@link #METADATA_KEY_COMPOSER}</li>
785          * <li>{@link #METADATA_KEY_DATE}</li>
786          * <li>{@link #METADATA_KEY_GENRE}</li>
787          * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
788          * <li>{@link #METADATA_KEY_ART_URI}</li>
789          * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
790          * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
791          * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
792          * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
793          * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
794          * </ul>
795          *
796          * @param key The key for referencing this value
797          * @param value The CharSequence value to store
798          * @return The Builder to allow chaining
799          */
putText(@extKey String key, CharSequence value)800         public Builder putText(@TextKey String key, CharSequence value) {
801             if (METADATA_KEYS_TYPE.containsKey(key)) {
802                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
803                     throw new IllegalArgumentException("The " + key
804                             + " key cannot be used to put a CharSequence");
805                 }
806             }
807             mBundle.putCharSequence(key, value);
808             return this;
809         }
810 
811         /**
812          * Put a String value into the metadata. Custom keys may be used, but if
813          * the METADATA_KEYs defined in this class are used they may only be one
814          * of the following:
815          * <ul>
816          * <li>{@link #METADATA_KEY_TITLE}</li>
817          * <li>{@link #METADATA_KEY_ARTIST}</li>
818          * <li>{@link #METADATA_KEY_ALBUM}</li>
819          * <li>{@link #METADATA_KEY_AUTHOR}</li>
820          * <li>{@link #METADATA_KEY_WRITER}</li>
821          * <li>{@link #METADATA_KEY_COMPOSER}</li>
822          * <li>{@link #METADATA_KEY_DATE}</li>
823          * <li>{@link #METADATA_KEY_GENRE}</li>
824          * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
825          * <li>{@link #METADATA_KEY_ART_URI}</li>
826          * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
827          * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
828          * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
829          * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
830          * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
831          * </ul>
832          * <p>
833          * Uris for artwork should use the content:// style and support
834          * {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork
835          * through {@link ContentResolver#openTypedAssetFileDescriptor(Uri,
836          * String, Bundle)}.
837          *
838          * @param key The key for referencing this value
839          * @param value The String value to store
840          * @return The Builder to allow chaining
841          */
putString(@extKey String key, String value)842         public Builder putString(@TextKey String key, String value) {
843             if (METADATA_KEYS_TYPE.containsKey(key)) {
844                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
845                     throw new IllegalArgumentException("The " + key
846                             + " key cannot be used to put a String");
847                 }
848             }
849             mBundle.putCharSequence(key, value);
850             return this;
851         }
852 
853         /**
854          * Put a long value into the metadata. Custom keys may be used, but if
855          * the METADATA_KEYs defined in this class are used they may only be one
856          * of the following:
857          * <ul>
858          * <li>{@link #METADATA_KEY_DURATION}</li>
859          * <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
860          * <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
861          * <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
862          * <li>{@link #METADATA_KEY_YEAR}</li>
863          * </ul>
864          *
865          * @param key The key for referencing this value
866          * @param value The long value to store
867          * @return The Builder to allow chaining
868          */
putLong(@ongKey String key, long value)869         public Builder putLong(@LongKey String key, long value) {
870             if (METADATA_KEYS_TYPE.containsKey(key)) {
871                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
872                     throw new IllegalArgumentException("The " + key
873                             + " key cannot be used to put a long");
874                 }
875             }
876             mBundle.putLong(key, value);
877             return this;
878         }
879 
880         /**
881          * Put a {@link Rating} into the metadata. Custom keys may be used, but
882          * if the METADATA_KEYs defined in this class are used they may only be
883          * one of the following:
884          * <ul>
885          * <li>{@link #METADATA_KEY_RATING}</li>
886          * <li>{@link #METADATA_KEY_USER_RATING}</li>
887          * </ul>
888          *
889          * @param key The key for referencing this value
890          * @param value The Rating value to store
891          * @return The Builder to allow chaining
892          */
putRating(@atingKey String key, Rating value)893         public Builder putRating(@RatingKey String key, Rating value) {
894             if (METADATA_KEYS_TYPE.containsKey(key)) {
895                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
896                     throw new IllegalArgumentException("The " + key
897                             + " key cannot be used to put a Rating");
898                 }
899             }
900             mBundle.putParcelable(key, value);
901             return this;
902         }
903 
904         /**
905          * Put a {@link Bitmap} into the metadata. Custom keys may be used, but
906          * if the METADATA_KEYs defined in this class are used they may only be
907          * one of the following:
908          * <ul>
909          * <li>{@link #METADATA_KEY_ART}</li>
910          * <li>{@link #METADATA_KEY_ALBUM_ART}</li>
911          * <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
912          * </ul>
913          * <p>
914          * Large bitmaps may be scaled down by the system with
915          * {@link Builder#setBitmapDimensionLimit(int)} when {@link MediaSession#setMetadata}
916          * is called. To pass full resolution images {@link Uri Uris} should be used with
917          * {@link #putString}.
918          *
919          * @param key The key for referencing this value
920          * @param value The Bitmap to store
921          * @return The Builder to allow chaining
922          */
putBitmap(@itmapKey String key, Bitmap value)923         public Builder putBitmap(@BitmapKey String key, Bitmap value) {
924             if (METADATA_KEYS_TYPE.containsKey(key)) {
925                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
926                     throw new IllegalArgumentException("The " + key
927                             + " key cannot be used to put a Bitmap");
928                 }
929             }
930             mBundle.putParcelable(key, value);
931             return this;
932         }
933 
934         /**
935          * Sets the maximum width/height (in pixels) for the bitmaps in the metadata.
936          * Bitmaps will be replaced with scaled down copies if their width (or height) is
937          * larger than {@code bitmapDimensionLimit}.
938          * <p>
939          * In order to unset the limit, pass {@link Integer#MAX_VALUE} as
940          * {@code bitmapDimensionLimit}.
941          *
942          * @param bitmapDimensionLimit The maximum width/height (in pixels) for bitmaps
943          *                             contained in the metadata. Non-positive values are ignored.
944          *                             Pass {@link Integer#MAX_VALUE} to unset the limit.
945          */
946         @NonNull
setBitmapDimensionLimit(@ntRangefrom = 1) int bitmapDimensionLimit)947         public Builder setBitmapDimensionLimit(@IntRange(from = 1) int bitmapDimensionLimit) {
948             if (bitmapDimensionLimit > 0) {
949                 mBitmapDimensionLimit = bitmapDimensionLimit;
950             } else {
951                 Log.w(TAG, "setBitmapDimensionLimit(): Ignoring non-positive bitmapDimensionLimit: "
952                         + bitmapDimensionLimit);
953             }
954             return this;
955         }
956 
957         /**
958          * Creates a {@link MediaMetadata} instance with the specified fields.
959          *
960          * @return The new MediaMetadata instance
961          */
build()962         public MediaMetadata build() {
963             if (mBitmapDimensionLimit != Integer.MAX_VALUE) {
964                 for (String key : mBundle.keySet()) {
965                     Object value = mBundle.get(key);
966                     if (value instanceof Bitmap) {
967                         Bitmap bmp = (Bitmap) value;
968                         if (bmp.getHeight() > mBitmapDimensionLimit
969                                 || bmp.getWidth() > mBitmapDimensionLimit) {
970                             putBitmap(key, scaleBitmap(bmp, mBitmapDimensionLimit));
971                         }
972                     }
973                 }
974             }
975             return new MediaMetadata(mBundle, mBitmapDimensionLimit);
976         }
977 
scaleBitmap(Bitmap bmp, int maxDimension)978         private Bitmap scaleBitmap(Bitmap bmp, int maxDimension) {
979             float maxDimensionF = maxDimension;
980             float widthScale = maxDimensionF / bmp.getWidth();
981             float heightScale = maxDimensionF / bmp.getHeight();
982             float scale = Math.min(widthScale, heightScale);
983             int height = (int) (bmp.getHeight() * scale);
984             int width = (int) (bmp.getWidth() * scale);
985             return Bitmap.createScaledBitmap(bmp, width, height, true);
986         }
987     }
988 }
989