/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jadn.cc.services; import android.app.PendingIntent; import android.graphics.Bitmap; import android.os.Looper; import android.util.Log; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * RemoteControlClient enables exposing information meant to be consumed by remote controls capable * of displaying metadata, artwork and media transport control buttons. A remote control client * object is associated with a media button event receiver. This event receiver must have been * previously registered with * {@link android.media.AudioManager#registerMediaButtonEventReceiver(android.content.ComponentName)} * before the RemoteControlClient can be registered through * {@link android.media.AudioManager#registerRemoteControlClient(android.media.RemoteControlClient)}. */ @SuppressWarnings({"rawtypes", "unchecked"}) public class RemoteControlClientCompat { private static final String TAG = "RemoteControlCompat"; private static Class sRemoteControlClientClass; // RCC short for RemoteControlClient private static Method sRCCEditMetadataMethod; private static Method sRCCSetPlayStateMethod; private static Method sRCCSetTransportControlFlags; private static boolean sHasRemoteControlAPIs = false; static { try { ClassLoader classLoader = RemoteControlClientCompat.class.getClassLoader(); sRemoteControlClientClass = getActualRemoteControlClientClass(classLoader); // dynamically populate the playstate and flag values in case they change // in future versions. for (Field field : RemoteControlClientCompat.class.getFields()) { try { Field realField = sRemoteControlClientClass.getField(field.getName()); Object realValue = realField.get(null); field.set(null, realValue); } catch (NoSuchFieldException e) { Log.w(TAG, "Could not get real field: " + field.getName()); } catch (IllegalArgumentException e) { Log.w(TAG, "Error trying to pull field value for: " + field.getName() + " " + e.getMessage()); } catch (IllegalAccessException e) { Log.w(TAG, "Error trying to pull field value for: " + field.getName() + " " + e.getMessage()); } } // get the required public methods on RemoteControlClient sRCCEditMetadataMethod = sRemoteControlClientClass.getMethod("editMetadata", boolean.class); sRCCSetPlayStateMethod = sRemoteControlClientClass.getMethod("setPlaybackState", int.class); sRCCSetTransportControlFlags = sRemoteControlClientClass.getMethod( "setTransportControlFlags", int.class); sHasRemoteControlAPIs = true; } catch (ClassNotFoundException e) { // Silently fail when running on an OS before ICS. } catch (NoSuchMethodException e) { // Silently fail when running on an OS before ICS. } catch (IllegalArgumentException e) { // Silently fail when running on an OS before ICS. } catch (SecurityException e) { // Silently fail when running on an OS before ICS. } } public static Class getActualRemoteControlClientClass(ClassLoader classLoader) throws ClassNotFoundException { return classLoader.loadClass("android.media.RemoteControlClient"); } private Object mActualRemoteControlClient; public RemoteControlClientCompat(PendingIntent pendingIntent) { if (!sHasRemoteControlAPIs) { return; } try { mActualRemoteControlClient = sRemoteControlClientClass.getConstructor(PendingIntent.class) .newInstance(pendingIntent); } catch (Exception e) { throw new RuntimeException(e); } } public RemoteControlClientCompat(PendingIntent pendingIntent, Looper looper) { if (!sHasRemoteControlAPIs) { return; } try { mActualRemoteControlClient = sRemoteControlClientClass.getConstructor(PendingIntent.class, Looper.class) .newInstance(pendingIntent, looper); } catch (Exception e) { Log.e(TAG, "Error creating new instance of " + sRemoteControlClientClass.getName(), e); } } /** * Class used to modify metadata in a {@link android.media.RemoteControlClient} object. Use * {@link android.media.RemoteControlClient#editMetadata(boolean)} to create an instance of an * editor, on which you set the metadata for the RemoteControlClient instance. Once all the * information has been set, use {@link #apply()} to make it the new metadata that should be * displayed for the associated client. Once the metadata has been "applied", you cannot reuse * this instance of the MetadataEditor. */ public class MetadataEditorCompat { private Method mPutStringMethod; private Method mPutBitmapMethod; private Method mPutLongMethod; private Method mClearMethod; private Method mApplyMethod; private Object mActualMetadataEditor; /** * The metadata key for the content artwork / album art. */ public final static int METADATA_KEY_ARTWORK = 100; private MetadataEditorCompat(Object actualMetadataEditor) { if (sHasRemoteControlAPIs && actualMetadataEditor == null) { throw new IllegalArgumentException("Remote Control API's exist, " + "should not be given a null MetadataEditor"); } if (sHasRemoteControlAPIs) { Class metadataEditorClass = actualMetadataEditor.getClass(); try { mPutStringMethod = metadataEditorClass.getMethod("putString", int.class, String.class); mPutBitmapMethod = metadataEditorClass.getMethod("putBitmap", int.class, Bitmap.class); mPutLongMethod = metadataEditorClass.getMethod("putLong", int.class, long.class); mClearMethod = metadataEditorClass.getMethod("clear", new Class[]{}); mApplyMethod = metadataEditorClass.getMethod("apply", new Class[]{}); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } mActualMetadataEditor = actualMetadataEditor; } /** * Adds textual information to be displayed. * Note that none of the information added after {@link #apply()} has been called, * will be displayed. * @param key The identifier of a the metadata field to set. Valid values are * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}. * @param value The text for the given key, or {@code null} to signify there is no valid * information for the field. * @return Returns a reference to the same MetadataEditor object, so you can chain put * calls together. */ public MetadataEditorCompat putString(int key, String value) { if (sHasRemoteControlAPIs) { try { mPutStringMethod.invoke(mActualMetadataEditor, key, value); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } return this; } /** * Sets the album / artwork picture to be displayed on the remote control. * @param key the identifier of the bitmap to set. The only valid value is * {@link #METADATA_KEY_ARTWORK} * @param bitmap The bitmap for the artwork, or null if there isn't any. * @return Returns a reference to the same MetadataEditor object, so you can chain put * calls together. * @throws IllegalArgumentException * @see android.graphics.Bitmap */ public MetadataEditorCompat putBitmap(int key, Bitmap bitmap) { if (sHasRemoteControlAPIs) { try { mPutBitmapMethod.invoke(mActualMetadataEditor, key, bitmap); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } return this; } /** * Adds numerical information to be displayed. * Note that none of the information added after {@link #apply()} has been called, * will be displayed. * @param key the identifier of a the metadata field to set. Valid values are * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value * expressed in milliseconds), * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. * @param value The long value for the given key * @return Returns a reference to the same MetadataEditor object, so you can chain put * calls together. * @throws IllegalArgumentException */ public MetadataEditorCompat putLong(int key, long value) { if (sHasRemoteControlAPIs) { try { mPutLongMethod.invoke(mActualMetadataEditor, key, value); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } return this; } /** * Clears all the metadata that has been set since the MetadataEditor instance was * created with {@link android.media.RemoteControlClient#editMetadata(boolean)}. */ public void clear() { if (sHasRemoteControlAPIs) { try { mClearMethod.invoke(mActualMetadataEditor, (Object[]) null); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } } /** * Associates all the metadata that has been set since the MetadataEditor instance was * created with {@link android.media.RemoteControlClient#editMetadata(boolean)}, or since * {@link #clear()} was called, with the RemoteControlClient. Once "applied", this * MetadataEditor cannot be reused to edit the RemoteControlClient's metadata. */ public void apply() { if (sHasRemoteControlAPIs) { try { mApplyMethod.invoke(mActualMetadataEditor, (Object[]) null); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } } } /** * Creates a {@link android.media.RemoteControlClient.MetadataEditor}. * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that * was previously applied to the RemoteControlClient, or true if it is to be created empty. * @return a new MetadataEditor instance. */ public MetadataEditorCompat editMetadata(boolean startEmpty) { Object metadataEditor; if (sHasRemoteControlAPIs) { try { metadataEditor = sRCCEditMetadataMethod.invoke(mActualRemoteControlClient, startEmpty); } catch (Exception e) { throw new RuntimeException(e); } } else { metadataEditor = null; } return new MetadataEditorCompat(metadataEditor); } /** * Sets the current playback state. * @param state The current playback state, one of the following values: * {@link android.media.RemoteControlClient#PLAYSTATE_STOPPED}, * {@link android.media.RemoteControlClient#PLAYSTATE_PAUSED}, * {@link android.media.RemoteControlClient#PLAYSTATE_PLAYING}, * {@link android.media.RemoteControlClient#PLAYSTATE_FAST_FORWARDING}, * {@link android.media.RemoteControlClient#PLAYSTATE_REWINDING}, * {@link android.media.RemoteControlClient#PLAYSTATE_SKIPPING_FORWARDS}, * {@link android.media.RemoteControlClient#PLAYSTATE_SKIPPING_BACKWARDS}, * {@link android.media.RemoteControlClient#PLAYSTATE_BUFFERING}, * {@link android.media.RemoteControlClient#PLAYSTATE_ERROR}. */ public void setPlaybackState(int state) { if (sHasRemoteControlAPIs) { try { sRCCSetPlayStateMethod.invoke(mActualRemoteControlClient, state); } catch (Exception e) { throw new RuntimeException(e); } } } /** * Sets the flags for the media transport control buttons that this client supports. * @param transportControlFlags A combination of the following flags: * {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PREVIOUS}, * {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_REWIND}, * {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY}, * {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY_PAUSE}, * {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PAUSE}, * {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_STOP}, * {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_FAST_FORWARD}, * {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_NEXT} */ public void setTransportControlFlags(int transportControlFlags) { if (sHasRemoteControlAPIs) { try { sRCCSetTransportControlFlags.invoke(mActualRemoteControlClient, transportControlFlags); } catch (Exception e) { throw new RuntimeException(e); } } } public final Object getActualRemoteControlClientObject() { return mActualRemoteControlClient; } }