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