content: /*
   * 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;
  	}
  }
  