1/*
2 * Copyright (C) 2015 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.hardware.radio;
17
18import android.annotation.NonNull;
19import android.annotation.SystemApi;
20import android.content.ContentResolver;
21import android.graphics.Bitmap;
22import android.graphics.BitmapFactory;
23import android.net.Uri;
24import android.os.Bundle;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.text.TextUtils;
28import android.util.ArrayMap;
29import android.util.Log;
30import android.util.SparseArray;
31
32import java.util.ArrayList;
33import java.util.Set;
34
35/**
36 * Contains meta data about a radio program such as station name, song title, artist etc...
37 * @hide
38 */
39@SystemApi
40public final class RadioMetadata implements Parcelable {
41    private static final String TAG = "RadioMetadata";
42
43    /**
44     * The RDS Program Information.
45     */
46    public static final String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI";
47
48    /**
49     * The RDS Program Service.
50     */
51    public static final String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS";
52
53    /**
54     * The RDS PTY.
55     */
56    public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY";
57
58    /**
59     * The RBDS PTY.
60     */
61    public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY";
62
63    /**
64     * The RBDS Radio Text.
65     */
66    public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT";
67
68    /**
69     * The song title.
70     */
71    public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE";
72
73    /**
74     * The artist name.
75     */
76    public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST";
77
78    /**
79     * The album name.
80     */
81    public static final String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM";
82
83    /**
84     * The music genre.
85     */
86    public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE";
87
88    /**
89     * The radio station icon {@link Bitmap}.
90     */
91    public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON";
92
93    /**
94     * The artwork for the song/album {@link Bitmap}.
95     */
96    public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART";
97
98
99    private static final int METADATA_TYPE_INVALID = -1;
100    private static final int METADATA_TYPE_INT = 0;
101    private static final int METADATA_TYPE_TEXT = 1;
102    private static final int METADATA_TYPE_BITMAP = 2;
103
104    private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
105
106    static {
107        METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
108        METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_TEXT);
109        METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT);
110        METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT);
111        METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT);
112        METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT);
113        METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
114        METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
115        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
116        METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
117        METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP);
118        METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
119    }
120
121    // keep in sync with: system/media/radio/include/system/radio_metadata.h
122    private static final int NATIVE_KEY_INVALID     = -1;
123    private static final int NATIVE_KEY_RDS_PI      = 0;
124    private static final int NATIVE_KEY_RDS_PS      = 1;
125    private static final int NATIVE_KEY_RDS_PTY     = 2;
126    private static final int NATIVE_KEY_RBDS_PTY    = 3;
127    private static final int NATIVE_KEY_RDS_RT      = 4;
128    private static final int NATIVE_KEY_TITLE       = 5;
129    private static final int NATIVE_KEY_ARTIST      = 6;
130    private static final int NATIVE_KEY_ALBUM       = 7;
131    private static final int NATIVE_KEY_GENRE       = 8;
132    private static final int NATIVE_KEY_ICON        = 9;
133    private static final int NATIVE_KEY_ART         = 10;
134
135    private static final SparseArray<String> NATIVE_KEY_MAPPING;
136
137    static {
138        NATIVE_KEY_MAPPING = new SparseArray<String>();
139        NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI);
140        NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS);
141        NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY);
142        NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY);
143        NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT);
144        NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE);
145        NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST);
146        NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM);
147        NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE);
148        NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON);
149        NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART);
150    }
151
152    private final Bundle mBundle;
153
154    RadioMetadata() {
155        mBundle = new Bundle();
156    }
157
158    private RadioMetadata(Bundle bundle) {
159        mBundle = new Bundle(bundle);
160    }
161
162    private RadioMetadata(Parcel in) {
163        mBundle = in.readBundle();
164    }
165
166    /**
167     * Returns {@code true} if the given key is contained in the meta data
168     *
169     * @param key a String key
170     * @return {@code true} if the key exists in this meta data, {@code false} otherwise
171     */
172    public boolean containsKey(String key) {
173        return mBundle.containsKey(key);
174    }
175
176    /**
177     * Returns the text value associated with the given key as a String, or null
178     * if the key is not found in the meta data.
179     *
180     * @param key The key the value is stored under
181     * @return a String value, or null
182     */
183    public String getString(String key) {
184        return mBundle.getString(key);
185    }
186
187    /**
188     * Returns the value associated with the given key,
189     * or 0 if the key is not found in the meta data.
190     *
191     * @param key The key the value is stored under
192     * @return an int value
193     */
194    public int getInt(String key) {
195        return mBundle.getInt(key, 0);
196    }
197
198    /**
199     * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data.
200     *
201     * @param key The key the value is stored under
202     * @return a {@link Bitmap} or null
203     */
204    public Bitmap getBitmap(String key) {
205        Bitmap bmp = null;
206        try {
207            bmp = mBundle.getParcelable(key);
208        } catch (Exception e) {
209            // ignore, value was not a bitmap
210            Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
211        }
212        return bmp;
213    }
214
215    @Override
216    public int describeContents() {
217        return 0;
218    }
219
220    @Override
221    public void writeToParcel(Parcel dest, int flags) {
222        dest.writeBundle(mBundle);
223    }
224
225    /**
226     * Returns the number of fields in this meta data.
227     *
228     * @return the number of fields in the meta data.
229     */
230    public int size() {
231        return mBundle.size();
232    }
233
234    /**
235     * Returns a Set containing the Strings used as keys in this meta data.
236     *
237     * @return a Set of String keys
238     */
239    public Set<String> keySet() {
240        return mBundle.keySet();
241    }
242
243    /**
244     * Helper for getting the String key used by {@link RadioMetadata} from the
245     * corrsponding native integer key.
246     *
247     * @param editorKey The key used by the editor
248     * @return the key used by this class or null if no mapping exists
249     * @hide
250     */
251    public static String getKeyFromNativeKey(int nativeKey) {
252        return NATIVE_KEY_MAPPING.get(nativeKey, null);
253    }
254
255    public static final Parcelable.Creator<RadioMetadata> CREATOR =
256            new Parcelable.Creator<RadioMetadata>() {
257                @Override
258                public RadioMetadata createFromParcel(Parcel in) {
259                    return new RadioMetadata(in);
260                }
261
262                @Override
263                public RadioMetadata[] newArray(int size) {
264                    return new RadioMetadata[size];
265                }
266            };
267
268    /**
269     * Use to build RadioMetadata objects.
270     */
271    public static final class Builder {
272        private final Bundle mBundle;
273
274        /**
275         * Create an empty Builder. Any field that should be included in the
276         * {@link RadioMetadata} must be added.
277         */
278        public Builder() {
279            mBundle = new Bundle();
280        }
281
282        /**
283         * Create a Builder using a {@link RadioMetadata} instance to set the
284         * initial values. All fields in the source meta data will be included in
285         * the new meta data. Fields can be overwritten by adding the same key.
286         *
287         * @param source
288         */
289        public Builder(RadioMetadata source) {
290            mBundle = new Bundle(source.mBundle);
291        }
292
293        /**
294         * Create a Builder using a {@link RadioMetadata} instance to set
295         * initial values, but replace bitmaps with a scaled down copy if they
296         * are larger than maxBitmapSize.
297         *
298         * @param source The original meta data to copy.
299         * @param maxBitmapSize The maximum height/width for bitmaps contained
300         *            in the meta data.
301         * @hide
302         */
303        public Builder(RadioMetadata source, int maxBitmapSize) {
304            this(source);
305            for (String key : mBundle.keySet()) {
306                Object value = mBundle.get(key);
307                if (value != null && value instanceof Bitmap) {
308                    Bitmap bmp = (Bitmap) value;
309                    if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
310                        putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
311                    }
312                }
313            }
314        }
315
316        /**
317         * Put a String value into the meta data. Custom keys may be used, but if
318         * the METADATA_KEYs defined in this class are used they may only be one
319         * of the following:
320         * <ul>
321         * <li>{@link #METADATA_KEY_RDS_PI}</li>
322         * <li>{@link #METADATA_KEY_RDS_PS}</li>
323         * <li>{@link #METADATA_KEY_RDS_RT}</li>
324         * <li>{@link #METADATA_KEY_TITLE}</li>
325         * <li>{@link #METADATA_KEY_ARTIST}</li>
326         * <li>{@link #METADATA_KEY_ALBUM}</li>
327         * <li>{@link #METADATA_KEY_GENRE}</li>
328         * </ul>
329         *
330         * @param key The key for referencing this value
331         * @param value The String value to store
332         * @return the same Builder instance
333         */
334        public Builder putString(String key, String value) {
335            if (!METADATA_KEYS_TYPE.containsKey(key) ||
336                    METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
337                throw new IllegalArgumentException("The " + key
338                        + " key cannot be used to put a String");
339            }
340            mBundle.putString(key, value);
341            return this;
342        }
343
344        /**
345         * Put an int value into the meta data. Custom keys may be used, but if
346         * the METADATA_KEYs defined in this class are used they may only be one
347         * of the following:
348         * <ul>
349         * <li>{@link #METADATA_KEY_RDS_PTY}</li>
350         * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
351         * </ul>
352         *
353         * @param key The key for referencing this value
354         * @param value The int value to store
355         * @return the same Builder instance
356         */
357        public Builder putInt(String key, int value) {
358            if (!METADATA_KEYS_TYPE.containsKey(key) ||
359                    METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_INT) {
360                throw new IllegalArgumentException("The " + key
361                        + " key cannot be used to put a long");
362            }
363            mBundle.putInt(key, value);
364            return this;
365        }
366
367        /**
368         * Put a {@link Bitmap} into the meta data. Custom keys may be used, but
369         * if the METADATA_KEYs defined in this class are used they may only be
370         * one of the following:
371         * <ul>
372         * <li>{@link #METADATA_KEY_ICON}</li>
373         * <li>{@link #METADATA_KEY_ART}</li>
374         * </ul>
375         * <p>
376         *
377         * @param key The key for referencing this value
378         * @param value The Bitmap to store
379         * @return the same Builder instance
380         */
381        public Builder putBitmap(String key, Bitmap value) {
382            if (!METADATA_KEYS_TYPE.containsKey(key) ||
383                    METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
384                throw new IllegalArgumentException("The " + key
385                        + " key cannot be used to put a Bitmap");
386            }
387            mBundle.putParcelable(key, value);
388            return this;
389        }
390
391        /**
392         * Creates a {@link RadioMetadata} instance with the specified fields.
393         *
394         * @return a new {@link RadioMetadata} object
395         */
396        public RadioMetadata build() {
397            return new RadioMetadata(mBundle);
398        }
399
400        private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
401            float maxSizeF = maxSize;
402            float widthScale = maxSizeF / bmp.getWidth();
403            float heightScale = maxSizeF / bmp.getHeight();
404            float scale = Math.min(widthScale, heightScale);
405            int height = (int) (bmp.getHeight() * scale);
406            int width = (int) (bmp.getWidth() * scale);
407            return Bitmap.createScaledBitmap(bmp, width, height, true);
408        }
409    }
410
411    int putIntFromNative(int nativeKey, int value) {
412        String key = getKeyFromNativeKey(nativeKey);
413        if (!METADATA_KEYS_TYPE.containsKey(key) ||
414                METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_INT) {
415            return -1;
416        }
417        mBundle.putInt(key, value);
418        return 0;
419    }
420
421    int putStringFromNative(int nativeKey, String value) {
422        String key = getKeyFromNativeKey(nativeKey);
423        if (!METADATA_KEYS_TYPE.containsKey(key) ||
424                METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
425            return -1;
426        }
427        mBundle.putString(key, value);
428        return 0;
429    }
430
431    int putBitmapFromNative(int nativeKey, byte[] value) {
432        String key = getKeyFromNativeKey(nativeKey);
433        if (!METADATA_KEYS_TYPE.containsKey(key) ||
434                METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
435            return -1;
436        }
437        Bitmap bmp = null;
438        try {
439            bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
440        } catch (Exception e) {
441        } finally {
442            if (bmp == null) {
443                return -1;
444            }
445            mBundle.putParcelable(key, bmp);
446            return 0;
447        }
448    }
449}
450