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 com.android.media;
18 
19 import static android.media.MediaMetadata2.*;
20 
21 import android.annotation.Nullable;
22 import android.graphics.Bitmap;
23 import android.media.MediaMetadata2;
24 import android.media.MediaMetadata2.BitmapKey;
25 import android.media.MediaMetadata2.Builder;
26 import android.media.MediaMetadata2.LongKey;
27 import android.media.MediaMetadata2.RatingKey;
28 import android.media.MediaMetadata2.TextKey;
29 import android.media.Rating2;
30 import android.media.update.MediaMetadata2Provider;
31 import android.os.Bundle;
32 import android.util.ArrayMap;
33 import android.util.Log;
34 
35 import java.util.Set;
36 
37 public class MediaMetadata2Impl implements MediaMetadata2Provider {
38     private static final String TAG = "MediaMetadata2";
39 
40     static final int METADATA_TYPE_LONG = 0;
41     static final int METADATA_TYPE_TEXT = 1;
42     static final int METADATA_TYPE_BITMAP = 2;
43     static final int METADATA_TYPE_RATING = 3;
44     static final int METADATA_TYPE_FLOAT = 4;
45     static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
46 
47     static {
48         METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT)49         METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT)50         METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG)51         METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT)52         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT)53         METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT)54         METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT)55         METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT)56         METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT)57         METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG)58         METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT)59         METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG)60         METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG)61         METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG)62         METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT)63         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP)64         METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT)65         METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP)66         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT)67         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING)68         METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING)69         METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT)70         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT)71         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT)72         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP)73         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT)74         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT)75         METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG)76         METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT)77         METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG)78         METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG)79         METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG);
80     }
81 
82     private static final @TextKey
83     String[] PREFERRED_DESCRIPTION_ORDER = {
84             METADATA_KEY_TITLE,
85             METADATA_KEY_ARTIST,
86             METADATA_KEY_ALBUM,
87             METADATA_KEY_ALBUM_ARTIST,
88             METADATA_KEY_WRITER,
89             METADATA_KEY_AUTHOR,
90             METADATA_KEY_COMPOSER
91     };
92 
93     private static final @BitmapKey
94     String[] PREFERRED_BITMAP_ORDER = {
95             METADATA_KEY_DISPLAY_ICON,
96             METADATA_KEY_ART,
97             METADATA_KEY_ALBUM_ART
98     };
99 
100     private static final @TextKey
101     String[] PREFERRED_URI_ORDER = {
102             METADATA_KEY_DISPLAY_ICON_URI,
103             METADATA_KEY_ART_URI,
104             METADATA_KEY_ALBUM_ART_URI
105     };
106 
107     private final MediaMetadata2 mInstance;
108     private final Bundle mBundle;
109 
MediaMetadata2Impl(Bundle bundle)110     public MediaMetadata2Impl(Bundle bundle) {
111         mInstance = new MediaMetadata2(this);
112         mBundle = bundle;
113     }
114 
getInstance()115     public MediaMetadata2 getInstance() {
116         return mInstance;
117     }
118 
119     @Override
containsKey_impl(String key)120     public boolean containsKey_impl(String key) {
121         if (key == null) {
122             throw new IllegalArgumentException("key shouldn't be null");
123         }
124         return mBundle.containsKey(key);
125     }
126 
127     @Override
getText_impl(@extKey String key)128     public CharSequence getText_impl(@TextKey String key) {
129         if (key == null) {
130             throw new IllegalArgumentException("key shouldn't be null");
131         }
132         return mBundle.getCharSequence(key);
133     }
134 
135     @Override
getMediaId_impl()136     public @Nullable String getMediaId_impl() {
137         return mInstance.getString(METADATA_KEY_MEDIA_ID);
138     }
139 
140     @Override
getString_impl(@extKey String key)141     public String getString_impl(@TextKey String key) {
142         if (key == null) {
143             throw new IllegalArgumentException("key shouldn't be null");
144         }
145         CharSequence text = mBundle.getCharSequence(key);
146         if (text != null) {
147             return text.toString();
148         }
149         return null;
150     }
151 
152     @Override
getLong_impl(@ongKey String key)153     public long getLong_impl(@LongKey String key) {
154         if (key == null) {
155             throw new IllegalArgumentException("key shouldn't be null");
156         }
157         return mBundle.getLong(key, 0);
158     }
159 
160     @Override
getRating_impl(@atingKey String key)161     public Rating2 getRating_impl(@RatingKey String key) {
162         if (key == null) {
163             throw new IllegalArgumentException("key shouldn't be null");
164         }
165         // TODO(jaewan): Add backward compatibility
166         Rating2 rating = null;
167         try {
168             rating = Rating2.fromBundle(mBundle.getBundle(key));
169         } catch (Exception e) {
170             // ignore, value was not a rating
171             Log.w(TAG, "Failed to retrieve a key as Rating.", e);
172         }
173         return rating;
174     }
175 
176     @Override
getFloat_impl(@loatKey String key)177     public float getFloat_impl(@FloatKey String key) {
178         if (key == null) {
179             throw new IllegalArgumentException("key shouldn't be null");
180         }
181         return mBundle.getFloat(key);
182     }
183 
184     @Override
getBitmap_impl(@itmapKey String key)185     public Bitmap getBitmap_impl(@BitmapKey String key) {
186         if (key == null) {
187             throw new IllegalArgumentException("key shouldn't be null");
188         }
189         Bitmap bmp = null;
190         try {
191             bmp = mBundle.getParcelable(key);
192         } catch (Exception e) {
193             // ignore, value was not a bitmap
194             Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
195         }
196         return bmp;
197     }
198 
199     @Override
getExtras_impl()200     public Bundle getExtras_impl() {
201         try {
202             return mBundle.getBundle(METADATA_KEY_EXTRAS);
203         } catch (Exception e) {
204             // ignore, value was not an bundle
205             Log.w(TAG, "Failed to retrieve an extra");
206         }
207         return null;
208     }
209 
210     @Override
size_impl()211     public int size_impl() {
212         return mBundle.size();
213     }
214 
215     @Override
keySet_impl()216     public Set<String> keySet_impl() {
217         return mBundle.keySet();
218     }
219 
220     @Override
toBundle_impl()221     public Bundle toBundle_impl() {
222         return mBundle;
223     }
224 
fromBundle_impl(Bundle bundle)225     public static MediaMetadata2 fromBundle_impl(Bundle bundle) {
226         return (bundle == null) ? null : new MediaMetadata2Impl(bundle).getInstance();
227     }
228 
229     public static final class BuilderImpl implements MediaMetadata2Provider.BuilderProvider {
230         private final MediaMetadata2.Builder mInstance;
231         private final Bundle mBundle;
232 
BuilderImpl(MediaMetadata2.Builder instance)233         public BuilderImpl(MediaMetadata2.Builder instance) {
234             mInstance = instance;
235             mBundle = new Bundle();
236         }
237 
BuilderImpl(MediaMetadata2.Builder instance, MediaMetadata2 source)238         public BuilderImpl(MediaMetadata2.Builder instance, MediaMetadata2 source) {
239             if (source == null) {
240                 throw new IllegalArgumentException("source shouldn't be null");
241             }
242             mInstance = instance;
243             mBundle = new Bundle(source.toBundle());
244         }
245 
BuilderImpl(int maxBitmapSize)246         public BuilderImpl(int maxBitmapSize) {
247             mInstance = new MediaMetadata2.Builder(this);
248             mBundle = new Bundle();
249 
250             for (String key : mBundle.keySet()) {
251                 Object value = mBundle.get(key);
252                 if (value instanceof Bitmap) {
253                     Bitmap bmp = (Bitmap) value;
254                     if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
255                         mInstance.putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
256                     }
257                 }
258             }
259         }
260 
261         @Override
putText_impl(@extKey String key, CharSequence value)262         public Builder putText_impl(@TextKey String key, CharSequence value) {
263             if (key == null) {
264                 throw new IllegalArgumentException("key shouldn't be null");
265             }
266             if (METADATA_KEYS_TYPE.containsKey(key)) {
267                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
268                     throw new IllegalArgumentException("The " + key
269                             + " key cannot be used to put a CharSequence");
270                 }
271             }
272             mBundle.putCharSequence(key, value);
273             return mInstance;
274         }
275 
276         @Override
putString_impl(@extKey String key, String value)277         public Builder putString_impl(@TextKey String key, String value) {
278             if (key == null) {
279                 throw new IllegalArgumentException("key shouldn't be null");
280             }
281             if (METADATA_KEYS_TYPE.containsKey(key)) {
282                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
283                     throw new IllegalArgumentException("The " + key
284                             + " key cannot be used to put a String");
285                 }
286             }
287             mBundle.putCharSequence(key, value);
288             return mInstance;
289         }
290 
291         @Override
putLong_impl(@ongKey String key, long value)292         public Builder putLong_impl(@LongKey String key, long value) {
293             if (key == null) {
294                 throw new IllegalArgumentException("key shouldn't be null");
295             }
296             if (METADATA_KEYS_TYPE.containsKey(key)) {
297                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
298                     throw new IllegalArgumentException("The " + key
299                             + " key cannot be used to put a long");
300                 }
301             }
302             mBundle.putLong(key, value);
303             return mInstance;
304         }
305 
306         @Override
putRating_impl(@atingKey String key, Rating2 value)307         public Builder putRating_impl(@RatingKey String key, Rating2 value) {
308             if (key == null) {
309                 throw new IllegalArgumentException("key shouldn't be null");
310             }
311             if (METADATA_KEYS_TYPE.containsKey(key)) {
312                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
313                     throw new IllegalArgumentException("The " + key
314                             + " key cannot be used to put a Rating");
315                 }
316             }
317             mBundle.putBundle(key, value.toBundle());
318             return mInstance;
319         }
320 
321         @Override
putBitmap_impl(@itmapKey String key, Bitmap value)322         public Builder putBitmap_impl(@BitmapKey String key, Bitmap value) {
323             if (key == null) {
324                 throw new IllegalArgumentException("key shouldn't be null");
325             }
326             if (METADATA_KEYS_TYPE.containsKey(key)) {
327                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
328                     throw new IllegalArgumentException("The " + key
329                             + " key cannot be used to put a Bitmap");
330                 }
331             }
332             mBundle.putParcelable(key, value);
333             return mInstance;
334         }
335 
336         @Override
putFloat_impl(@loatKey String key, float value)337         public Builder putFloat_impl(@FloatKey String key, float value) {
338             if (key == null) {
339                 throw new IllegalArgumentException("key shouldn't be null");
340             }
341             if (METADATA_KEYS_TYPE.containsKey(key)) {
342                 if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_FLOAT) {
343                     throw new IllegalArgumentException("The " + key
344                             + " key cannot be used to put a float");
345                 }
346             }
347             mBundle.putFloat(key, value);
348             return mInstance;
349         }
350 
351         @Override
setExtras_impl(Bundle bundle)352         public Builder setExtras_impl(Bundle bundle) {
353             mBundle.putBundle(METADATA_KEY_EXTRAS, bundle);
354             return mInstance;
355         }
356 
357         @Override
build_impl()358         public MediaMetadata2 build_impl() {
359             return new MediaMetadata2Impl(mBundle).getInstance();
360         }
361 
scaleBitmap(Bitmap bmp, int maxSize)362         private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
363             float maxSizeF = maxSize;
364             float widthScale = maxSizeF / bmp.getWidth();
365             float heightScale = maxSizeF / bmp.getHeight();
366             float scale = Math.min(widthScale, heightScale);
367             int height = (int) (bmp.getHeight() * scale);
368             int width = (int) (bmp.getWidth() * scale);
369             return Bitmap.createScaledBitmap(bmp, width, height, true);
370         }
371     }
372 }
373 
374