Home | History | Annotate | Download | only in musicplayer
      1 /*
      2  * Copyright (C) 2011 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.example.android.musicplayer;
     18 
     19 import android.app.PendingIntent;
     20 import android.graphics.Bitmap;
     21 import android.os.Looper;
     22 import android.util.Log;
     23 
     24 import java.lang.reflect.Field;
     25 import java.lang.reflect.Method;
     26 
     27 /**
     28  * RemoteControlClient enables exposing information meant to be consumed by remote controls capable
     29  * of displaying metadata, artwork and media transport control buttons. A remote control client
     30  * object is associated with a media button event receiver. This event receiver must have been
     31  * previously registered with
     32  * {@link android.media.AudioManager#registerMediaButtonEventReceiver(android.content.ComponentName)}
     33  * before the RemoteControlClient can be registered through
     34  * {@link android.media.AudioManager#registerRemoteControlClient(android.media.RemoteControlClient)}.
     35  */
     36 @SuppressWarnings({"rawtypes", "unchecked"})
     37 public class RemoteControlClientCompat {
     38 
     39     private static final String TAG = "RemoteControlCompat";
     40 
     41     private static Class sRemoteControlClientClass;
     42 
     43     // RCC short for RemoteControlClient
     44     private static Method sRCCEditMetadataMethod;
     45     private static Method sRCCSetPlayStateMethod;
     46     private static Method sRCCSetTransportControlFlags;
     47 
     48     private static boolean sHasRemoteControlAPIs = false;
     49 
     50     static {
     51         try {
     52             ClassLoader classLoader = RemoteControlClientCompat.class.getClassLoader();
     53             sRemoteControlClientClass = getActualRemoteControlClientClass(classLoader);
     54             // dynamically populate the playstate and flag values in case they change
     55             // in future versions.
     56             for (Field field : RemoteControlClientCompat.class.getFields()) {
     57                 try {
     58                     Field realField = sRemoteControlClientClass.getField(field.getName());
     59                     Object realValue = realField.get(null);
     60                     field.set(null, realValue);
     61                 } catch (NoSuchFieldException e) {
     62                     Log.w(TAG, "Could not get real field: " + field.getName());
     63                 } catch (IllegalArgumentException e) {
     64                     Log.w(TAG, "Error trying to pull field value for: " + field.getName()
     65                             + " " + e.getMessage());
     66                 } catch (IllegalAccessException e) {
     67                     Log.w(TAG, "Error trying to pull field value for: " + field.getName()
     68                             + " " + e.getMessage());
     69                 }
     70             }
     71 
     72             // get the required public methods on RemoteControlClient
     73             sRCCEditMetadataMethod = sRemoteControlClientClass.getMethod("editMetadata",
     74                     boolean.class);
     75             sRCCSetPlayStateMethod = sRemoteControlClientClass.getMethod("setPlaybackState",
     76                     int.class);
     77             sRCCSetTransportControlFlags = sRemoteControlClientClass.getMethod(
     78                     "setTransportControlFlags", int.class);
     79 
     80             sHasRemoteControlAPIs = true;
     81         } catch (ClassNotFoundException e) {
     82             // Silently fail when running on an OS before ICS.
     83         } catch (NoSuchMethodException e) {
     84             // Silently fail when running on an OS before ICS.
     85         } catch (IllegalArgumentException e) {
     86             // Silently fail when running on an OS before ICS.
     87         } catch (SecurityException e) {
     88             // Silently fail when running on an OS before ICS.
     89         }
     90     }
     91 
     92     public static Class getActualRemoteControlClientClass(ClassLoader classLoader)
     93             throws ClassNotFoundException {
     94         return classLoader.loadClass("android.media.RemoteControlClient");
     95     }
     96 
     97     private Object mActualRemoteControlClient;
     98 
     99     public RemoteControlClientCompat(PendingIntent pendingIntent) {
    100         if (!sHasRemoteControlAPIs) {
    101             return;
    102         }
    103         try {
    104             mActualRemoteControlClient =
    105                     sRemoteControlClientClass.getConstructor(PendingIntent.class)
    106                             .newInstance(pendingIntent);
    107         } catch (Exception e) {
    108             throw new RuntimeException(e);
    109         }
    110     }
    111 
    112     public RemoteControlClientCompat(PendingIntent pendingIntent, Looper looper) {
    113         if (!sHasRemoteControlAPIs) {
    114             return;
    115         }
    116 
    117         try {
    118             mActualRemoteControlClient =
    119                     sRemoteControlClientClass.getConstructor(PendingIntent.class, Looper.class)
    120                             .newInstance(pendingIntent, looper);
    121         } catch (Exception e) {
    122             Log.e(TAG, "Error creating new instance of " + sRemoteControlClientClass.getName(), e);
    123         }
    124     }
    125 
    126     /**
    127      * Class used to modify metadata in a {@link android.media.RemoteControlClient} object. Use
    128      * {@link android.media.RemoteControlClient#editMetadata(boolean)} to create an instance of an
    129      * editor, on which you set the metadata for the RemoteControlClient instance. Once all the
    130      * information has been set, use {@link #apply()} to make it the new metadata that should be
    131      * displayed for the associated client. Once the metadata has been "applied", you cannot reuse
    132      * this instance of the MetadataEditor.
    133      */
    134     public class MetadataEditorCompat {
    135 
    136         private Method mPutStringMethod;
    137         private Method mPutBitmapMethod;
    138         private Method mPutLongMethod;
    139         private Method mClearMethod;
    140         private Method mApplyMethod;
    141 
    142         private Object mActualMetadataEditor;
    143 
    144         /**
    145          * The metadata key for the content artwork / album art.
    146          */
    147         public final static int METADATA_KEY_ARTWORK = 100;
    148 
    149         private MetadataEditorCompat(Object actualMetadataEditor) {
    150             if (sHasRemoteControlAPIs && actualMetadataEditor == null) {
    151                 throw new IllegalArgumentException("Remote Control API's exist, " +
    152                         "should not be given a null MetadataEditor");
    153             }
    154             if (sHasRemoteControlAPIs) {
    155                 Class metadataEditorClass = actualMetadataEditor.getClass();
    156 
    157                 try {
    158                     mPutStringMethod = metadataEditorClass.getMethod("putString",
    159                             int.class, String.class);
    160                     mPutBitmapMethod = metadataEditorClass.getMethod("putBitmap",
    161                             int.class, Bitmap.class);
    162                     mPutLongMethod = metadataEditorClass.getMethod("putLong",
    163                             int.class, long.class);
    164                     mClearMethod = metadataEditorClass.getMethod("clear", new Class[]{});
    165                     mApplyMethod = metadataEditorClass.getMethod("apply", new Class[]{});
    166                 } catch (Exception e) {
    167                     throw new RuntimeException(e.getMessage(), e);
    168                 }
    169             }
    170             mActualMetadataEditor = actualMetadataEditor;
    171         }
    172 
    173         /**
    174          * Adds textual information to be displayed.
    175          * Note that none of the information added after {@link #apply()} has been called,
    176          * will be displayed.
    177          * @param key The identifier of a the metadata field to set. Valid values are
    178          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
    179          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
    180          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
    181          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
    182          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
    183          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
    184          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
    185          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
    186          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
    187          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
    188          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
    189          * @param value The text for the given key, or {@code null} to signify there is no valid
    190          *      information for the field.
    191          * @return Returns a reference to the same MetadataEditor object, so you can chain put
    192          *      calls together.
    193          */
    194         public MetadataEditorCompat putString(int key, String value) {
    195             if (sHasRemoteControlAPIs) {
    196                 try {
    197                     mPutStringMethod.invoke(mActualMetadataEditor, key, value);
    198                 } catch (Exception e) {
    199                     throw new RuntimeException(e.getMessage(), e);
    200                 }
    201             }
    202             return this;
    203         }
    204 
    205         /**
    206          * Sets the album / artwork picture to be displayed on the remote control.
    207          * @param key the identifier of the bitmap to set. The only valid value is
    208          *      {@link #METADATA_KEY_ARTWORK}
    209          * @param bitmap The bitmap for the artwork, or null if there isn't any.
    210          * @return Returns a reference to the same MetadataEditor object, so you can chain put
    211          *      calls together.
    212          * @throws IllegalArgumentException
    213          * @see android.graphics.Bitmap
    214          */
    215         public MetadataEditorCompat putBitmap(int key, Bitmap bitmap) {
    216             if (sHasRemoteControlAPIs) {
    217                 try {
    218                     mPutBitmapMethod.invoke(mActualMetadataEditor, key, bitmap);
    219                 } catch (Exception e) {
    220                     throw new RuntimeException(e.getMessage(), e);
    221                 }
    222             }
    223             return this;
    224         }
    225 
    226         /**
    227          * Adds numerical information to be displayed.
    228          * Note that none of the information added after {@link #apply()} has been called,
    229          * will be displayed.
    230          * @param key the identifier of a the metadata field to set. Valid values are
    231          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
    232          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
    233          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
    234          *      expressed in milliseconds),
    235          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
    236          * @param value The long value for the given key
    237          * @return Returns a reference to the same MetadataEditor object, so you can chain put
    238          *      calls together.
    239          * @throws IllegalArgumentException
    240          */
    241         public MetadataEditorCompat putLong(int key, long value) {
    242             if (sHasRemoteControlAPIs) {
    243                 try {
    244                     mPutLongMethod.invoke(mActualMetadataEditor, key, value);
    245                 } catch (Exception e) {
    246                     throw new RuntimeException(e.getMessage(), e);
    247                 }
    248             }
    249             return this;
    250         }
    251 
    252         /**
    253          * Clears all the metadata that has been set since the MetadataEditor instance was
    254          * created with {@link android.media.RemoteControlClient#editMetadata(boolean)}.
    255          */
    256         public void clear() {
    257             if (sHasRemoteControlAPIs) {
    258                 try {
    259                     mClearMethod.invoke(mActualMetadataEditor, (Object[]) null);
    260                 } catch (Exception e) {
    261                     throw new RuntimeException(e.getMessage(), e);
    262                 }
    263             }
    264         }
    265 
    266         /**
    267          * Associates all the metadata that has been set since the MetadataEditor instance was
    268          * created with {@link android.media.RemoteControlClient#editMetadata(boolean)}, or since
    269          * {@link #clear()} was called, with the RemoteControlClient. Once "applied", this
    270          * MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
    271          */
    272         public void apply() {
    273             if (sHasRemoteControlAPIs) {
    274                 try {
    275                     mApplyMethod.invoke(mActualMetadataEditor, (Object[]) null);
    276                 } catch (Exception e) {
    277                     throw new RuntimeException(e.getMessage(), e);
    278                 }
    279             }
    280         }
    281     }
    282 
    283     /**
    284      * Creates a {@link android.media.RemoteControlClient.MetadataEditor}.
    285      * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
    286      *     was previously applied to the RemoteControlClient, or true if it is to be created empty.
    287      * @return a new MetadataEditor instance.
    288      */
    289     public MetadataEditorCompat editMetadata(boolean startEmpty) {
    290         Object metadataEditor;
    291         if (sHasRemoteControlAPIs) {
    292             try {
    293                 metadataEditor = sRCCEditMetadataMethod.invoke(mActualRemoteControlClient,
    294                         startEmpty);
    295             } catch (Exception e) {
    296                 throw new RuntimeException(e);
    297             }
    298         } else {
    299             metadataEditor = null;
    300         }
    301         return new MetadataEditorCompat(metadataEditor);
    302     }
    303 
    304     /**
    305      * Sets the current playback state.
    306      * @param state The current playback state, one of the following values:
    307      *       {@link android.media.RemoteControlClient#PLAYSTATE_STOPPED},
    308      *       {@link android.media.RemoteControlClient#PLAYSTATE_PAUSED},
    309      *       {@link android.media.RemoteControlClient#PLAYSTATE_PLAYING},
    310      *       {@link android.media.RemoteControlClient#PLAYSTATE_FAST_FORWARDING},
    311      *       {@link android.media.RemoteControlClient#PLAYSTATE_REWINDING},
    312      *       {@link android.media.RemoteControlClient#PLAYSTATE_SKIPPING_FORWARDS},
    313      *       {@link android.media.RemoteControlClient#PLAYSTATE_SKIPPING_BACKWARDS},
    314      *       {@link android.media.RemoteControlClient#PLAYSTATE_BUFFERING},
    315      *       {@link android.media.RemoteControlClient#PLAYSTATE_ERROR}.
    316      */
    317     public void setPlaybackState(int state) {
    318         if (sHasRemoteControlAPIs) {
    319             try {
    320                 sRCCSetPlayStateMethod.invoke(mActualRemoteControlClient, state);
    321             } catch (Exception e) {
    322                 throw new RuntimeException(e);
    323             }
    324         }
    325     }
    326 
    327     /**
    328      * Sets the flags for the media transport control buttons that this client supports.
    329      * @param transportControlFlags A combination of the following flags:
    330      *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PREVIOUS},
    331      *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_REWIND},
    332      *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY},
    333      *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY_PAUSE},
    334      *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PAUSE},
    335      *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_STOP},
    336      *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_FAST_FORWARD},
    337      *      {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_NEXT}
    338      */
    339     public void setTransportControlFlags(int transportControlFlags) {
    340         if (sHasRemoteControlAPIs) {
    341             try {
    342                 sRCCSetTransportControlFlags.invoke(mActualRemoteControlClient,
    343                         transportControlFlags);
    344             } catch (Exception e) {
    345                 throw new RuntimeException(e);
    346             }
    347         }
    348     }
    349 
    350     public final Object getActualRemoteControlClientObject() {
    351         return mActualRemoteControlClient;
    352     }
    353 }
    354