MediaMetadataEditor.java revision 88183e67d4628e8c8a3310af0076b6f33f955cb2
1/*
2 * Copyright (C) 2013 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
17package android.media;
18
19import android.graphics.Bitmap;
20import android.os.Bundle;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.util.Log;
24import android.util.SparseIntArray;
25
26/**
27 * An abstract class for editing and storing metadata that can be published by
28 * {@link RemoteControlClient}. See the {@link RemoteControlClient#editMetadata(boolean)}
29 * method to instantiate a {@link RemoteControlClient.MetadataEditor} object.
30 */
31public abstract class MediaMetadataEditor {
32
33    private final static String TAG = "MediaMetadataEditor";
34    /**
35     * @hide
36     */
37    protected MediaMetadataEditor() {
38    }
39
40    // Public keys for metadata used by RemoteControlClient and RemoteController.
41    // Note that these keys are defined here, and not in MediaMetadataRetriever
42    // because they are not supported by the MediaMetadataRetriever features.
43    /**
44     * The metadata key for the content artwork / album art.
45     */
46    public final static int BITMAP_KEY_ARTWORK =
47            RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK;
48
49    /**
50     * The metadata key for the content's average rating, not the user's rating.
51     * The value associated with this key is a {@link Rating} instance.
52     * @see #RATING_KEY_BY_USER
53     */
54    public final static int RATING_KEY_BY_OTHERS = 101;
55
56    /**
57     * The metadata key for the content's user rating.
58     * The value associated with this key is a {@link Rating} instance.
59     * This key can be flagged as "editable" (with {@link #addEditableKey(int)}) to enable
60     * receiving user rating values through the
61     * {@link android.media.RemoteControlClient.OnMetadataUpdateListener} interface.
62     */
63    public final static int RATING_KEY_BY_USER = 0x10000001;
64
65    /**
66     * @hide
67     * Editable key mask
68     */
69    public final static int KEY_EDITABLE_MASK = 0x1FFFFFFF;
70
71
72    /**
73     * Applies all of the metadata changes that have been set since the MediaMetadataEditor instance
74     * was created or since {@link #clear()} was called.
75     */
76    public abstract void apply();
77
78
79    /**
80     * @hide
81     * Mask of editable keys.
82     */
83    protected long mEditableKeys;
84
85    /**
86     * @hide
87     */
88    protected boolean mMetadataChanged = false;
89
90    /**
91     * @hide
92     */
93    protected boolean mApplied = false;
94
95    /**
96     * @hide
97     */
98    protected boolean mArtworkChanged = false;
99
100    /**
101     * @hide
102     */
103    protected Bitmap mEditorArtwork;
104
105    /**
106     * @hide
107     */
108    protected Bundle mEditorMetadata;
109
110
111    /**
112     * Clears all the pending metadata changes set since the MediaMetadataEditor instance was
113     * created or since this method was last called.
114     * Note that clearing the metadata doesn't reset the editable keys
115     * (use {@link #removeEditableKeys()} instead).
116     */
117    public synchronized void clear() {
118        if (mApplied) {
119            Log.e(TAG, "Can't clear a previously applied MediaMetadataEditor");
120            return;
121        }
122        mEditorMetadata.clear();
123        mEditorArtwork = null;
124    }
125
126    /**
127     * Flags the given key as being editable.
128     * This should only be used by metadata publishers, such as {@link RemoteControlClient},
129     * which will declare the metadata field as eligible to be updated, with new values
130     * received through the {@link RemoteControlClient.OnMetadataUpdateListener} interface.
131     * @param key the type of metadata that can be edited. The supported key is
132     *     {@link #RATING_KEY_BY_USER}.
133     */
134    public synchronized void addEditableKey(int key) {
135        if (mApplied) {
136            Log.e(TAG, "Can't change editable keys of a previously applied MetadataEditor");
137            return;
138        }
139        // only one editable key at the moment, so we're not wasting memory on an array
140        // of editable keys to check the validity of the key, just hardcode the supported key.
141        if (key == RATING_KEY_BY_USER) {
142            mEditableKeys |= (KEY_EDITABLE_MASK & key);
143            mMetadataChanged = true;
144        } else {
145            Log.e(TAG, "Metadata key " + key + " cannot be edited");
146        }
147    }
148
149    /**
150     * Causes all metadata fields to be read-only.
151     */
152    public synchronized void removeEditableKeys() {
153        if (mApplied) {
154            Log.e(TAG, "Can't remove all editable keys of a previously applied MetadataEditor");
155            return;
156        }
157        if (mEditableKeys != 0) {
158            mEditableKeys = 0;
159            mMetadataChanged = true;
160        }
161    }
162
163    /**
164     * Retrieves the keys flagged as editable.
165     * @return null if there are no editable keys, or an array containing the keys.
166     */
167    public synchronized int[] getEditableKeys() {
168        // only one editable key supported here
169        if (mEditableKeys == RATING_KEY_BY_USER) {
170            int[] keys = { RATING_KEY_BY_USER };
171            return keys;
172        } else {
173            return null;
174        }
175    }
176
177    /**
178     * Adds textual information.
179     * Note that none of the information added after {@link #apply()} has been called,
180     * will be available to consumers of metadata stored by the MediaMetadataEditor.
181     * @param key The identifier of a the metadata field to set. Valid values are
182     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
183     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
184     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
185     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
186     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
187     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
188     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
189     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
190     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
191     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
192     * @param value The text for the given key, or {@code null} to signify there is no valid
193     *      information for the field.
194     * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put
195     *      calls together.
196     */
197    public synchronized MediaMetadataEditor putString(int key, String value)
198            throws IllegalArgumentException {
199        if (mApplied) {
200            Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor");
201            return this;
202        }
203        if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_STRING) {
204            throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
205        }
206        mEditorMetadata.putString(String.valueOf(key), value);
207        mMetadataChanged = true;
208        return this;
209    }
210
211    /**
212     * Adds numerical information.
213     * Note that none of the information added after {@link #apply()} has been called
214     * will be available to consumers of metadata stored by the MediaMetadataEditor.
215     * @param key the identifier of a the metadata field to set. Valid values are
216     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
217     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
218     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
219     *      expressed in milliseconds),
220     *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
221     * @param value The long value for the given key
222     * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put
223     *      calls together.
224     * @throws IllegalArgumentException
225     */
226    public synchronized MediaMetadataEditor putLong(int key, long value)
227            throws IllegalArgumentException {
228        if (mApplied) {
229            Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor");
230            return this;
231        }
232        if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_LONG) {
233            throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
234        }
235        mEditorMetadata.putLong(String.valueOf(key), value);
236        mMetadataChanged = true;
237        return this;
238    }
239
240    /**
241     * Adds image.
242     * @param key the identifier of the bitmap to set. The only valid value is
243     *      {@link #BITMAP_KEY_ARTWORK}
244     * @param bitmap The bitmap for the artwork, or null if there isn't any.
245     * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put
246     *      calls together.
247     * @throws IllegalArgumentException
248     * @see android.graphics.Bitmap
249     */
250    public synchronized MediaMetadataEditor putBitmap(int key, Bitmap bitmap)
251            throws IllegalArgumentException {
252        if (mApplied) {
253            Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor");
254            return this;
255        }
256        if (key != BITMAP_KEY_ARTWORK) {
257            throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
258        }
259        mEditorArtwork = bitmap;
260        mArtworkChanged = true;
261        return this;
262    }
263
264    /**
265     * Adds information stored as an instance.
266     * Note that none of the information added after {@link #apply()} has been called
267     * will be available to consumers of metadata stored by the MediaMetadataEditor.
268     * @param key the identifier of a the metadata field to set. Valid keys for a:
269     *     <ul>
270     *     <li>{@link Bitmap} object are {@link #BITMAP_KEY_ARTWORK},</li>
271     *     <li>{@link String} object are the same as for {@link #putString(int, String)}</li>
272     *     <li>{@link Long} object are the same as for {@link #putLong(int, long)}</li>
273     *     <li>{@link Rating} object are {@link #RATING_KEY_BY_OTHERS}
274     *         and {@link #RATING_KEY_BY_USER}.</li>
275     *     </ul>
276     * @param obj the metadata to add.
277     * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put
278     *      calls together.
279     * @throws IllegalArgumentException
280     */
281    public synchronized MediaMetadataEditor putObject(int key, Object value)
282            throws IllegalArgumentException {
283        if (mApplied) {
284            Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor");
285            return this;
286        }
287        switch(METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID)) {
288            case METADATA_TYPE_LONG:
289                if (value instanceof Long) {
290                    return putLong(key, ((Long)value).longValue());
291                } else {
292                    throw(new IllegalArgumentException("Not a non-null Long for key "+ key));
293                }
294            case METADATA_TYPE_STRING:
295                if ((value == null) || (value instanceof String)) {
296                    return putString(key, (String) value);
297                } else {
298                    throw(new IllegalArgumentException("Not a String for key "+ key));
299                }
300            case METADATA_TYPE_RATING:
301                mEditorMetadata.putParcelable(String.valueOf(key), (Parcelable)value);
302                mMetadataChanged = true;
303                break;
304            case METADATA_TYPE_BITMAP:
305                if ((value == null) || (value instanceof Bitmap))  {
306                    return putBitmap(key, (Bitmap) value);
307                } else {
308                    throw(new IllegalArgumentException("Not a Bitmap for key "+ key));
309                }
310            default:
311                throw(new IllegalArgumentException("Invalid key "+ key));
312        }
313        return this;
314    }
315
316
317    /**
318     * Returns the long value for the key.
319     * @param key one of the keys supported in {@link #putLong(int, long)}
320     * @param defaultValue the value returned if the key is not present
321     * @return the long value for the key, or the supplied default value if the key is not present
322     * @throws IllegalArgumentException
323     */
324    public synchronized long getLong(int key, long defaultValue)
325            throws IllegalArgumentException {
326        if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_LONG) {
327            throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
328        }
329        return mEditorMetadata.getLong(String.valueOf(key), defaultValue);
330    }
331
332    /**
333     * Returns the {@link String} value for the key.
334     * @param key one of the keys supported in {@link #putString(int, String)}
335     * @param defaultValue the value returned if the key is not present
336     * @return the {@link String} value for the key, or the supplied default value if the key is
337     *     not present
338     * @throws IllegalArgumentException
339     */
340    public synchronized String getString(int key, String defaultValue)
341            throws IllegalArgumentException {
342        if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_STRING) {
343            throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
344        }
345        return mEditorMetadata.getString(String.valueOf(key), defaultValue);
346    }
347
348    /**
349     * Returns the {@link Bitmap} value for the key.
350     * @param key the {@link #BITMAP_KEY_ARTWORK} key
351     * @param defaultValue the value returned if the key is not present
352     * @return the {@link Bitmap} value for the key, or the supplied default value if the key is
353     *     not present
354     * @throws IllegalArgumentException
355     */
356    public synchronized Bitmap getBitmap(int key, Bitmap defaultValue)
357            throws IllegalArgumentException {
358        if (key != BITMAP_KEY_ARTWORK) {
359            throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
360        }
361        return (mEditorArtwork != null ? mEditorArtwork : defaultValue);
362    }
363
364    /**
365     * Returns an object representation of the value for the key
366     * @param key one of the keys supported in {@link #putObject(int, Object)}
367     * @param defaultValue the value returned if the key is not present
368     * @return the object for the key, as a {@link Long}, {@link Bitmap}, {@link String}, or
369     *     {@link Rating} depending on the key value, or the supplied default value if the key is
370     *     not present
371     * @throws IllegalArgumentException
372     */
373    public synchronized Object getObject(int key, Object defaultValue)
374            throws IllegalArgumentException {
375        switch (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID)) {
376            case METADATA_TYPE_LONG:
377                if (mEditorMetadata.containsKey(String.valueOf(key))) {
378                    return mEditorMetadata.getLong(String.valueOf(key));
379                } else {
380                    return defaultValue;
381                }
382            case METADATA_TYPE_STRING:
383                if (mEditorMetadata.containsKey(String.valueOf(key))) {
384                    return mEditorMetadata.getString(String.valueOf(key));
385                } else {
386                    return defaultValue;
387                }
388            case METADATA_TYPE_RATING:
389                if (mEditorMetadata.containsKey(String.valueOf(key))) {
390                    return mEditorMetadata.getParcelable(String.valueOf(key));
391                } else {
392                    return defaultValue;
393                }
394            case METADATA_TYPE_BITMAP:
395                // only one key for Bitmap supported, value is not stored in mEditorMetadata Bundle
396                if (key == BITMAP_KEY_ARTWORK) {
397                    return (mEditorArtwork != null ? mEditorArtwork : defaultValue);
398                } // else: fall through to invalid key handling
399            default:
400                throw(new IllegalArgumentException("Invalid key "+ key));
401        }
402    }
403
404
405    /**
406     * @hide
407     */
408    protected static final int METADATA_TYPE_INVALID = -1;
409    /**
410     * @hide
411     */
412    protected static final int METADATA_TYPE_LONG = 0;
413
414    /**
415     * @hide
416     */
417    protected static final int METADATA_TYPE_STRING = 1;
418
419    /**
420     * @hide
421     */
422    protected static final int METADATA_TYPE_BITMAP = 2;
423
424    /**
425     * @hide
426     */
427    protected static final int METADATA_TYPE_RATING = 3;
428
429    /**
430     * @hide
431     */
432    protected static final SparseIntArray METADATA_KEYS_TYPE;
433
434    static {
435        METADATA_KEYS_TYPE = new SparseIntArray(17);
436        // NOTE: if adding to the list below, make sure you increment the array initialization size
437        // keys with long values
438        METADATA_KEYS_TYPE.put(
439                MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, METADATA_TYPE_LONG);
440        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
441        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DURATION, METADATA_TYPE_LONG);
442        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_YEAR, METADATA_TYPE_LONG);
443        // keys with String values
444        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_ALBUM, METADATA_TYPE_STRING);
445        METADATA_KEYS_TYPE.put(
446                MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, METADATA_TYPE_STRING);
447        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_TITLE, METADATA_TYPE_STRING);
448        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_ARTIST, METADATA_TYPE_STRING);
449        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_AUTHOR, METADATA_TYPE_STRING);
450        METADATA_KEYS_TYPE.put(
451                MediaMetadataRetriever.METADATA_KEY_COMPILATION, METADATA_TYPE_STRING);
452        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_COMPOSER, METADATA_TYPE_STRING);
453        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DATE, METADATA_TYPE_STRING);
454        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_GENRE, METADATA_TYPE_STRING);
455        METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_WRITER, METADATA_TYPE_STRING);
456        // keys with Bitmap values
457        METADATA_KEYS_TYPE.put(BITMAP_KEY_ARTWORK, METADATA_TYPE_BITMAP);
458        // keys with Rating values
459        METADATA_KEYS_TYPE.put(RATING_KEY_BY_OTHERS, METADATA_TYPE_RATING);
460        METADATA_KEYS_TYPE.put(RATING_KEY_BY_USER, METADATA_TYPE_RATING);
461    }
462}
463