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