/* * 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.kure.musicplayer.external; import java.lang.reflect.Field; import java.lang.reflect.Method; import android.app.PendingIntent; import android.graphics.Bitmap; import android.os.Looper; import android.util.Log; //NOTE (READ THIS PLEASE): // //This file was directly copied from the Android //sample project "Random Music Player". // //I use it to implement the Lock Screen "Widget" //(control the music player on the lock-screen) // //Take a look at my service that plays songs to //see how I use this class. /** * Sends info to a thing that has remote media * controls (such as the Lock-Screen Widget). * * A remote control client object is associated with a * media button event receiver. * * This event receiver must have been previously registered with * `AudioManager#registerMediaButtonEventReceiver` before the * `RemoteControlClient` can be registered through * `AudioManager#registerRemoteControlClient`. * */ @SuppressWarnings({"rawtypes", "unchecked"}) public class RemoteControlClientCompat { // Used for debugging 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 `RemoteControlClient` object. * * Use `RemoteControlClient#editMetadata` 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 `#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 {@code 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). * * @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; } }