summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Michel Trivi <jmtrivi@google.com>2011-08-18 19:16:47 -0700
committerJean-Michel Trivi <jmtrivi@google.com>2011-08-23 11:23:22 -0700
commit4426e42ac6107bf6b09f7c4cdad39eb161d8b9ca (patch)
tree27528bb34ea1b83f0c798ac975fd27c38f5869c1
parent0197d825f15844e51b91a0006d411ecb06350641 (diff)
downloadframeworks_base-4426e42ac6107bf6b09f7c4cdad39eb161d8b9ca.zip
frameworks_base-4426e42ac6107bf6b09f7c4cdad39eb161d8b9ca.tar.gz
frameworks_base-4426e42ac6107bf6b09f7c4cdad39eb161d8b9ca.tar.bz2
Bug 5045498 New implementation of remote control API
Remote control displays expose an IRemoteControlDisplay interface which they register through AudioManager. Remote control clients create a RemoteControlClient object, which implicitely exposes an IRemoteControlClient interface registered in AudioService through AudioManager. AudioService tells all clients and displays when a new client is the one that should be displayed. A client's data gets sent to the display when it is valid, or it sets new data (while being valid). The implementation for setting metadata and album art is temporary, and will migrate to the MetadataEditor API in future CLs. Change-Id: Ibab6ea1d94c68f32482c760c6ae269541f885548
-rw-r--r--Android.mk3
-rw-r--r--CleanSpec.mk1
-rw-r--r--media/java/android/media/AudioManager.java168
-rw-r--r--media/java/android/media/AudioService.java297
-rw-r--r--media/java/android/media/IAudioService.aidl16
-rw-r--r--media/java/android/media/IRemoteControlClient.aidl51
-rw-r--r--media/java/android/media/IRemoteControlClientDispatcher.aidl92
-rw-r--r--media/java/android/media/IRemoteControlDisplay.aidl42
-rw-r--r--media/java/android/media/RemoteControlClient.java557
9 files changed, 853 insertions, 374 deletions
diff --git a/Android.mk b/Android.mk
index fdf0933..d4dc088 100644
--- a/Android.mk
+++ b/Android.mk
@@ -183,7 +183,8 @@ LOCAL_SRC_FILES += \
media/java/android/media/IAudioFocusDispatcher.aidl \
media/java/android/media/IMediaScannerListener.aidl \
media/java/android/media/IMediaScannerService.aidl \
- media/java/android/media/IRemoteControlClientDispatcher.aidl \
+ media/java/android/media/IRemoteControlClient.aidl \
+ media/java/android/media/IRemoteControlDisplay.aidl \
telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
telephony/java/com/android/internal/telephony/ITelephony.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 2593065..3cec66f 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -105,6 +105,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/SystemUI_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/R/com/android/systemui/R.java)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IAudioService.P)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IAudioService.P)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f9efd3c..e3ef717 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1715,161 +1715,54 @@ public class AudioManager {
}
}
- /**
- * Acts as a proxy between AudioService and the RemoteControlClient
- */
- private IRemoteControlClientDispatcher mRcClientDispatcher =
- new IRemoteControlClientDispatcher.Stub() {
-
- public String getMetadataStringForClient(String clientName, int field) {
- RemoteControlClient realClient;
- synchronized(mRcClientMap) {
- realClient = mRcClientMap.get(clientName);
- }
- if (realClient != null) {
- return realClient.getMetadataString(field);
- } else {
- return null;
- }
- }
-
- public int getPlaybackStateForClient(String clientName) {
- RemoteControlClient realClient;
- synchronized(mRcClientMap) {
- realClient = mRcClientMap.get(clientName);
- }
- if (realClient != null) {
- return realClient.getPlaybackState();
- } else {
- return 0;
- }
- }
-
- public int getTransportControlFlagsForClient(String clientName) {
- RemoteControlClient realClient;
- synchronized(mRcClientMap) {
- realClient = mRcClientMap.get(clientName);
- }
- if (realClient != null) {
- return realClient.getTransportControlFlags();
- } else {
- return 0;
- }
- }
-
- public Bitmap getAlbumArtForClient(String clientName, int maxWidth, int maxHeight) {
- RemoteControlClient realClient;
- synchronized(mRcClientMap) {
- realClient = mRcClientMap.get(clientName);
- }
- if (realClient != null) {
- return realClient.getAlbumArt(maxWidth, maxHeight);
- } else {
- return null;
- }
- }
- };
-
- private HashMap<String, RemoteControlClient> mRcClientMap =
- new HashMap<String, RemoteControlClient>();
-
- private String getIdForRcClient(RemoteControlClient client) {
- // client is guaranteed to be non-null
- return client.toString();
- }
/**
* @hide
+ * CANDIDATE FOR SDK
* Registers the remote control client for providing information to display on the remote
* controls.
- * @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
- * that will receive the media button intent, and associated with the remote control
- * client. This method has no effect if
- * {@link #registerMediaButtonEventReceiver(ComponentName)} hasn't been called
- * with the same eventReceiver, or if
- * {@link #unregisterMediaButtonEventReceiver(ComponentName)} has been called.
- * @param rcClient the remote control client associated with the event receiver, responsible
+ * @param rcClient the remote control client associated responsible
* for providing the information to display on the remote control.
*/
- public void registerRemoteControlClient(ComponentName eventReceiver,
- RemoteControlClient rcClient) {
- if ((eventReceiver == null) || (rcClient == null)) {
+ public void registerRemoteControlClient(RemoteControlClient rcClient) {
+ if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) {
return;
}
- String clientKey = getIdForRcClient(rcClient);
- synchronized(mRcClientMap) {
- if (mRcClientMap.containsKey(clientKey)) {
- return;
- }
- mRcClientMap.put(clientKey, rcClient);
- }
IAudioService service = getService();
try {
- service.registerRemoteControlClient(eventReceiver, mRcClientDispatcher, clientKey,
+ service.registerRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */
+ rcClient.getIRemoteControlClient(), /* rcClient */
+ rcClient.toString(), /* clientName */
// used to match media button event receiver and audio focus
- mContext.getPackageName());
+ mContext.getPackageName()); /* packageName */
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerRemoteControlClient"+e);
- synchronized(mRcClientMap) {
- mRcClientMap.remove(clientKey);
- }
}
}
/**
* @hide
+ * CANDIDATE FOR SDK
* Unregisters the remote control client that was providing information to display on the
* remotes.
- * @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
- * that receives the media button intent, and associated with the remote control
- * client.
* @param rcClient the remote control client to unregister
- * @see #registerRemoteControlClient(ComponentName, RemoteControlClient)
+ * @see #registerRemoteControlClient(RemoteControlClient)
*/
- public void unregisterRemoteControlClient(ComponentName eventReceiver,
- RemoteControlClient rcClient) {
- if ((eventReceiver == null) || (rcClient == null)) {
+ public void unregisterRemoteControlClient(RemoteControlClient rcClient) {
+ if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) {
return;
}
IAudioService service = getService();
try {
- // remove locally
- boolean unregister = true;
- synchronized(mRcClientMap) {
- if (mRcClientMap.remove(getIdForRcClient(rcClient)) == null) {
- unregister = false;
- }
- }
- if (unregister) {
- // unregistering a RemoteControlClient is equivalent to setting it to null
- service.registerRemoteControlClient(eventReceiver, null, null,
- mContext.getPackageName());
- }
+ service.unregisterRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */
+ rcClient.getIRemoteControlClient()); /* rcClient */
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e);
}
}
- /**
- * @hide
- * Returns the current remote control client.
- * @param rcClientId the generation counter that matches the extra
- * {@link AudioManager#EXTRA_REMOTE_CONTROL_CLIENT_GENERATION} in the
- * {@link AudioManager#REMOTE_CONTROL_CLIENT_CHANGED} event
- * @return the current RemoteControlClient from which information to display on the remote
- * control can be retrieved, or null if rcClientId doesn't match the current generation
- * counter.
- */
- public IRemoteControlClientDispatcher getRemoteControlClientDispatcher(int rcClientId) {
- IAudioService service = getService();
- try {
- return service.getRemoteControlClientDispatcher(rcClientId);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in getRemoteControlClient "+e);
- return null;
- }
- }
+ // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
/**
* @hide
* Broadcast intent action indicating that the displays on the remote controls
@@ -1882,6 +1775,7 @@ public class AudioManager {
public static final String REMOTE_CONTROL_CLIENT_CHANGED =
"android.media.REMOTE_CONTROL_CLIENT_CHANGED";
+ // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
/**
* @hide
* The IRemoteControlClientDispatcher monotonically increasing generation counter.
@@ -1891,6 +1785,7 @@ public class AudioManager {
public static final String EXTRA_REMOTE_CONTROL_CLIENT_GENERATION =
"android.media.EXTRA_REMOTE_CONTROL_CLIENT_GENERATION";
+ // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
/**
* @hide
* The name of the RemoteControlClient.
@@ -1902,6 +1797,7 @@ public class AudioManager {
public static final String EXTRA_REMOTE_CONTROL_CLIENT_NAME =
"android.media.EXTRA_REMOTE_CONTROL_CLIENT_NAME";
+ // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
/**
* @hide
* The media button event receiver associated with the RemoteControlClient.
@@ -1913,6 +1809,7 @@ public class AudioManager {
public static final String EXTRA_REMOTE_CONTROL_EVENT_RECEIVER =
"android.media.EXTRA_REMOTE_CONTROL_EVENT_RECEIVER";
+ // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
/**
* @hide
* The flags describing what information has changed in the current remote control client.
@@ -1923,33 +1820,6 @@ public class AudioManager {
"android.media.EXTRA_REMOTE_CONTROL_CLIENT_INFO_CHANGED";
/**
- * @hide
- * Notifies the users of the associated remote control client that the information to display
- * has changed.
- @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
- * that will receive the media button intent, and associated with the remote control
- * client. This method has no effect if
- * {@link #registerMediaButtonEventReceiver(ComponentName)} hasn't been called
- * with the same eventReceiver, or if
- * {@link #unregisterMediaButtonEventReceiver(ComponentName)} has been called.
- * @param infoFlag the type of information that has changed since this method was last called,
- * or the event receiver was registered. Use one or multiple of the following flags to
- * describe what changed:
- * {@link RemoteControlClient#FLAG_INFORMATION_CHANGED_METADATA},
- * {@link RemoteControlClient#FLAG_INFORMATION_CHANGED_KEY_MEDIA},
- * {@link RemoteControlClient#FLAG_INFORMATION_CHANGED_PLAYSTATE},
- * {@link RemoteControlClient#FLAG_INFORMATION_CHANGED_ALBUM_ART}.
- */
- public void notifyRemoteControlInformationChanged(ComponentName eventReceiver, int infoFlag) {
- IAudioService service = getService();
- try {
- service.notifyRemoteControlInformationChanged(eventReceiver, infoFlag);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in refreshRemoteControlDisplay"+e);
- }
- }
-
- /**
* @hide
* Reload audio settings. This method is called by Settings backup
* agent when audio settings are restored and causes the AudioService
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 85c7dba..834e8c1 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -34,7 +34,6 @@ import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
-import android.media.IRemoteControlClientDispatcher;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
@@ -2170,45 +2169,12 @@ public class AudioService extends IAudioService.Stub {
break;
case MSG_RCDISPLAY_CLEAR:
- // TODO remove log before release
- Log.i(TAG, "Clear remote control display");
- Intent clearIntent = new Intent(AudioManager.REMOTE_CONTROL_CLIENT_CHANGED);
- // no extra means no IRemoteControlClientDispatcher, which is a request to clear
- clearIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcast(clearIntent);
+ onRcDisplayClear();
break;
case MSG_RCDISPLAY_UPDATE:
- synchronized(mCurrentRcLock) {
- // msg.obj is guaranteed to be non null
- RemoteControlStackEntry rcse = (RemoteControlStackEntry)msg.obj;
- if ((mCurrentRcClient == null) ||
- (!mCurrentRcClient.equals(rcse.mRcClient))) {
- // the remote control display owner has changed between the
- // the message to update the display was sent, and the time it
- // gets to be processed (now)
- } else {
- mCurrentRcClientGen++;
- // TODO remove log before release
- Log.i(TAG, "Display/update remote control ");
- Intent rcClientIntent = new Intent(
- AudioManager.REMOTE_CONTROL_CLIENT_CHANGED);
- rcClientIntent.putExtra(
- AudioManager.EXTRA_REMOTE_CONTROL_CLIENT_GENERATION,
- mCurrentRcClientGen);
- rcClientIntent.putExtra(
- AudioManager.EXTRA_REMOTE_CONTROL_CLIENT_INFO_CHANGED,
- msg.arg1);
- rcClientIntent.putExtra(
- AudioManager.EXTRA_REMOTE_CONTROL_EVENT_RECEIVER,
- rcse.mReceiverComponent.flattenToString());
- rcClientIntent.putExtra(
- AudioManager.EXTRA_REMOTE_CONTROL_CLIENT_NAME,
- rcse.mRcClientName);
- rcClientIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcast(rcClientIntent);
- }
- }
+ // msg.obj is guaranteed to be non null
+ onRcDisplayUpdate( (RemoteControlStackEntry) msg.obj, msg.arg1);
break;
case MSG_BT_HEADSET_CNCT_FAILED:
@@ -2896,18 +2862,18 @@ public class AudioService extends IAudioService.Stub {
private final Object mCurrentRcLock = new Object();
/**
- * The one remote control client to be polled for display information.
+ * The one remote control client which will receive a request for display information.
* This object may be null.
* Access protected by mCurrentRcLock.
*/
- private IRemoteControlClientDispatcher mCurrentRcClient = null;
+ private IRemoteControlClient mCurrentRcClient = null;
private final static int RC_INFO_NONE = 0;
private final static int RC_INFO_ALL =
- RemoteControlClient.FLAG_INFORMATION_CHANGED_ALBUM_ART |
- RemoteControlClient.FLAG_INFORMATION_CHANGED_KEY_MEDIA |
- RemoteControlClient.FLAG_INFORMATION_CHANGED_METADATA |
- RemoteControlClient.FLAG_INFORMATION_CHANGED_PLAYSTATE;
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART |
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA |
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA |
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE;
/**
* A monotonically increasing generation counter for mCurrentRcClient.
@@ -2917,25 +2883,6 @@ public class AudioService extends IAudioService.Stub {
private int mCurrentRcClientGen = 0;
/**
- * Returns the current remote control client.
- * @param rcClientId the counter value that matches the extra
- * {@link AudioManager#EXTRA_REMOTE_CONTROL_CLIENT_GENERATION} in the
- * {@link AudioManager#REMOTE_CONTROL_CLIENT_CHANGED} event
- * @return the current IRemoteControlClientDispatcher from which information to display on the
- * remote control can be retrieved, or null if rcClientId doesn't match the current
- * generation counter.
- */
- public IRemoteControlClientDispatcher getRemoteControlClientDispatcher(int rcClientId) {
- synchronized(mCurrentRcLock) {
- if (rcClientId == mCurrentRcClientGen) {
- return mCurrentRcClient;
- } else {
- return null;
- }
- }
- }
-
- /**
* Inner class to monitor remote control client deaths, and remove the client for the
* remote control stack if necessary.
*/
@@ -2968,7 +2915,7 @@ public class AudioService extends IAudioService.Stub {
public int mCallingUid;
/** provides access to the information to display on the remote control */
- public IRemoteControlClientDispatcher mRcClient;
+ public IRemoteControlClient mRcClient;
public RcClientDeathHandler mRcClientDeathHandler;
public RemoteControlStackEntry(ComponentName r) {
@@ -3122,6 +3069,103 @@ public class AudioService extends IAudioService.Stub {
return false;
}
+ //==========================================================================================
+ // Remote control display / client
+ //==========================================================================================
+ /**
+ * Update the remote control displays with the new "focused" client generation
+ */
+ private void setNewRcClientGenerationOnDisplays_syncRcStack(int newClientGeneration) {
+ // NOTE: Only one IRemoteControlDisplay supported in this implementation
+ if (mRcDisplay != null) {
+ try {
+ mRcDisplay.setCurrentClientGenerationId(newClientGeneration);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead display in onRcDisplayUpdate() "+e);
+ // if we had a display before, stop monitoring its death
+ rcDisplay_stopDeathMonitor_syncRcStack();
+ mRcDisplay = null;
+ }
+ }
+ }
+
+ /**
+ * Update the remote control clients with the new "focused" client generation
+ */
+ private void setNewRcClientGenerationOnClients_syncRcStack(int newClientGeneration) {
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry se = stackIterator.next();
+ if ((se != null) && (se.mRcClient != null)) {
+ try {
+ se.mRcClient.setCurrentClientGenerationId(newClientGeneration);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Dead client in onRcDisplayUpdate()"+e);
+ stackIterator.remove();
+ se.unlinkToRcClientDeath();
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the displays and clients with the new "focused" client generation
+ */
+ private void setNewRcClientGeneration(int newClientGeneration) {
+ synchronized(mRCStack) {
+ // send the new valid client generation ID to all displays
+ setNewRcClientGenerationOnDisplays_syncRcStack(newClientGeneration);
+ // send the new valid client generation ID to all clients
+ setNewRcClientGenerationOnClients_syncRcStack(newClientGeneration);
+ }
+ }
+
+ /**
+ * Called when processing MSG_RCDISPLAY_CLEAR event
+ */
+ private void onRcDisplayClear() {
+ // TODO remove log before release
+ Log.i(TAG, "Clear remote control display");
+
+ synchronized(mCurrentRcLock) {
+ mCurrentRcClientGen++;
+
+ // synchronously update the displays and clients with the new client generation
+ setNewRcClientGeneration(mCurrentRcClientGen);
+ }
+ }
+
+ /**
+ * Called when processing MSG_RCDISPLAY_UPDATE event
+ */
+ private void onRcDisplayUpdate(RemoteControlStackEntry rcse, int flags /* USED ?*/) {
+ synchronized(mCurrentRcLock) {
+ if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(rcse.mRcClient))) {
+ // TODO remove log before release
+ Log.i(TAG, "Display/update remote control ");
+
+ mCurrentRcClientGen++;
+
+ // synchronously update the displays and clients with the new client generation
+ setNewRcClientGeneration(mCurrentRcClientGen);
+
+ // ask the current client that it needs to send info
+ try {
+ mCurrentRcClient.onInformationRequested(mCurrentRcClientGen,
+ flags, mArtworkExpectedWidth, mArtworkExpectedHeight);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Current valid remote client is dead: "+e);
+ mCurrentRcClient = null;
+ }
+ } else {
+ // the remote control display owner has changed between the
+ // the message to update the display was sent, and the time it
+ // gets to be processed (now)
+ }
+ }
+ }
+
+
/**
* Helper function:
* Called synchronized on mRCStack
@@ -3130,6 +3174,7 @@ public class AudioService extends IAudioService.Stub {
synchronized(mCurrentRcLock) {
mCurrentRcClient = null;
}
+ // will cause onRcDisplayClear() to be called in AudioService's handler thread
mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) );
}
@@ -3155,6 +3200,7 @@ public class AudioService extends IAudioService.Stub {
}
mCurrentRcClient = rcse.mRcClient;
}
+ // will cause onRcDisplayUpdate() to be called in AudioService's handler thread
mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_RCDISPLAY_UPDATE,
infoFlagsAboutToBeUsed /* arg1 */, 0, rcse /* obj, != null */) );
}
@@ -3223,7 +3269,7 @@ public class AudioService extends IAudioService.Stub {
/** see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) */
public void registerRemoteControlClient(ComponentName eventReceiver,
- IRemoteControlClientDispatcher rcClient, String clientName, String callingPackageName) {
+ IRemoteControlClient rcClient, String clientName, String callingPackageName) {
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
// store the new display information
@@ -3238,6 +3284,14 @@ public class AudioService extends IAudioService.Stub {
}
// save the new remote control client
rcse.mRcClient = rcClient;
+ if (mRcDisplay != null) {
+ try {
+ rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error connecting remote control display to client: "+e);
+ e.printStackTrace();
+ }
+ }
rcse.mCallingPackageName = callingPackageName;
rcse.mRcClientName = clientName;
rcse.mCallingUid = Binder.getCallingUid();
@@ -3269,18 +3323,121 @@ public class AudioService extends IAudioService.Stub {
}
}
- /** see AudioManager.notifyRemoteControlInformationChanged(ComponentName er, int infoFlag) */
- public void notifyRemoteControlInformationChanged(ComponentName eventReceiver, int infoFlag) {
- synchronized(mAudioFocusLock) {
+ /** see AudioManager.unregisterRemoteControlClient(ComponentName eventReceiver, ...) */
+ public void unregisterRemoteControlClient(ComponentName eventReceiver,
+ IRemoteControlClient rcClient) {
+ //FIXME implement
+ }
+
+ /**
+ * The remote control displays.
+ * Access synchronized on mRCStack
+ * NOTE: Only one IRemoteControlDisplay supported in this implementation
+ */
+ private IRemoteControlDisplay mRcDisplay;
+ private RcDisplayDeathHandler mRcDisplayDeathHandler;
+ private int mArtworkExpectedWidth = -1;
+ private int mArtworkExpectedHeight = -1;
+ /**
+ * Inner class to monitor remote control display deaths, and unregister them from the list
+ * of displays if necessary.
+ */
+ private class RcDisplayDeathHandler implements IBinder.DeathRecipient {
+ public void binderDied() {
synchronized(mRCStack) {
- // only refresh if the eventReceiver is at the top of the stack
- if (isCurrentRcController(eventReceiver)) {
- checkUpdateRemoteControlDisplay(infoFlag);
+ Log.w(TAG, " RemoteControl: display died");
+ mRcDisplay = null;
+ }
+ }
+
+ }
+
+ private void rcDisplay_stopDeathMonitor_syncRcStack() {
+ if (mRcDisplay != null) {
+ // we had a display before, stop monitoring its death
+ IBinder b = mRcDisplay.asBinder();
+ try {
+ b.unlinkToDeath(mRcDisplayDeathHandler, 0);
+ } catch (java.util.NoSuchElementException e) {
+ // being conservative here
+ Log.e(TAG, "Error while trying to unlink display death handler " + e);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void rcDisplay_startDeathMonitor_syncRcStack() {
+ if (mRcDisplay != null) {
+ // new non-null display, monitor its death
+ IBinder b = mRcDisplay.asBinder();
+ mRcDisplayDeathHandler = new RcDisplayDeathHandler();
+ try {
+ b.linkToDeath(mRcDisplayDeathHandler, 0);
+ } catch (RemoteException e) {
+ // remote control display is DOA, disqualify it
+ Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + b);
+ mRcDisplay = null;
+ }
+ }
+ }
+
+ public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ synchronized(mRCStack) {
+ if (mRcDisplay == rcd) {
+ return;
+ }
+ // if we had a display before, stop monitoring its death
+ rcDisplay_stopDeathMonitor_syncRcStack();
+ mRcDisplay = rcd;
+ // new display, start monitoring its death
+ rcDisplay_startDeathMonitor_syncRcStack();
+
+ // let all the remote control clients there is a new display
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ if(rcse.mRcClient != null) {
+ try {
+ rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error connecting remote control display to client: " + e);
+ e.printStackTrace();
+ }
}
}
}
}
+ public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ synchronized(mRCStack) {
+ // if we had a display before, stop monitoring its death
+ rcDisplay_stopDeathMonitor_syncRcStack();
+ mRcDisplay = null;
+
+ // disconnect this remote control display from all the clients
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ if(rcse.mRcClient != null) {
+ try {
+ rcse.mRcClient.unplugRemoteControlDisplay(rcd);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error disconnecting remote control display to client: " + e);
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+ public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
+ synchronized(mRCStack) {
+ // NOTE: Only one IRemoteControlDisplay supported in this implementation
+ mArtworkExpectedWidth = w;
+ mArtworkExpectedHeight = h;
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
// TODO probably a lot more to do here than just the audio focus and remote control stacks
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7f9ced9..7bf9814 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -18,7 +18,8 @@ package android.media;
import android.content.ComponentName;
import android.media.IAudioFocusDispatcher;
-import android.media.IRemoteControlClientDispatcher;
+import android.media.IRemoteControlClient;
+import android.media.IRemoteControlDisplay;
/**
* {@hide}
@@ -88,13 +89,14 @@ interface IAudioService {
void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver);
- void registerRemoteControlClient(in ComponentName eventReceiver,
- in IRemoteControlClientDispatcher rcClient, in String clientName,
- in String callingPackageName);
+ oneway void registerRemoteControlClient(in ComponentName eventReceiver,
+ in IRemoteControlClient rcClient, in String clientName, in String callingPackageName);
+ oneway void unregisterRemoteControlClient(in ComponentName eventReceiver,
+ in IRemoteControlClient rcClient);
- IRemoteControlClientDispatcher getRemoteControlClientDispatcher(in int rcClientId);
-
- void notifyRemoteControlInformationChanged(in ComponentName eventReceiver, int infoFlag);
+ oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd);
+ oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd);
+ oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h);
void startBluetoothSco(IBinder cb);
diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl
new file mode 100644
index 0000000..0fbba20
--- /dev/null
+++ b/media/java/android/media/IRemoteControlClient.aidl
@@ -0,0 +1,51 @@
+/* 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 android.media;
+
+import android.graphics.Bitmap;
+import android.media.IRemoteControlDisplay;
+
+/**
+ * @hide
+ * Interface registered by AudioManager to notify a source of remote control information
+ * that information is requested to be displayed on the remote control (through
+ * IRemoteControlDisplay).
+ * {@see AudioManager#registerRemoteControlClient(RemoteControlClient)}.
+ */
+oneway interface IRemoteControlClient
+{
+ /**
+ * Notifies a remote control client that information for the given generation ID is
+ * requested. If the flags contains
+ * {@link RemoteControlClient#FLAG_INFORMATION_REQUESTED_ALBUM_ART} then the width and height
+ * parameters are valid.
+ * @param generationId
+ * @param infoFlags
+ * @param artWidth if > 0, artHeight must be > 0 too.
+ * @param artHeight
+ * FIXME: is infoFlags required? since the RCC pushes info, this might always be called
+ * with RC_INFO_ALL
+ */
+ void onInformationRequested(int generationId, int infoFlags, int artWidth, int artHeight);
+
+ /**
+ * Sets the generation counter of the current client that is displayed on the remote control.
+ */
+ void setCurrentClientGenerationId(int clientGeneration);
+
+ void plugRemoteControlDisplay(IRemoteControlDisplay rcd);
+ void unplugRemoteControlDisplay(IRemoteControlDisplay rcd);
+} \ No newline at end of file
diff --git a/media/java/android/media/IRemoteControlClientDispatcher.aidl b/media/java/android/media/IRemoteControlClientDispatcher.aidl
deleted file mode 100644
index 98142cc..0000000
--- a/media/java/android/media/IRemoteControlClientDispatcher.aidl
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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 android.media;
-
-import android.graphics.Bitmap;
-
-/**
- * @hide
- * Interface registered by AudioManager to dispatch remote control information requests
- * to the RemoteControlClient implementation. This is used by AudioService.
- * {@see AudioManager#registerRemoteControlClient(ComponentName, RemoteControlClient)}.
- */
-interface IRemoteControlClientDispatcher
-{
- /**
- * Called by a remote control to retrieve a String of information to display.
- * @param field the identifier for a metadata field to retrieve. 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_CD_TRACK_NUMBER},
- * {@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_DISC_NUMBER},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
- * @return null if the requested field is not supported, or the String matching the
- * metadata field.
- */
- String getMetadataStringForClient(String clientName, int field);
-
- /**
- * Called by a remote control to retrieve the current playback state.
- * @return one of the following values:
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_STOPPED},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_PAUSED},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_PLAYING},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_FAST_FORWARDING},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_REWINDING},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_SKIPPING_FORWARDS},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_SKIPPING_BACKWARDS},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_BUFFERING},
- * {@link android.media.AudioManager.RemoteControlParameters#PLAYSTATE_ERROR}.
- */
- int getPlaybackStateForClient(String clientName);
-
- /**
- * Called by a remote control to retrieve the flags for the media transport control buttons
- * that this client supports.
- * @see {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_PREVIOUS},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_REWIND},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_PLAY},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_PLAY_PAUSE},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_PAUSE},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_STOP},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_FAST_FORWARD},
- * {@link android.media.AudioManager.RemoteControlParameters#FLAG_KEY_MEDIA_NEXT}
- */
- int getTransportControlFlagsForClient(String clientName);
-
- /**
- * Called by a remote control to retrieve the album art picture at the requested size.
- * Note that returning a bitmap smaller than the maximum requested dimension is accepted
- * and it will be scaled as needed, but exceeding the maximum dimensions may produce
- * unspecified results, such as the image being cropped or simply not being displayed.
- * @param maxWidth the maximum width of the requested bitmap expressed in pixels.
- * @param maxHeight the maximum height of the requested bitmap expressed in pixels.
- * @return the bitmap for the album art, or null if there isn't any.
- * @see android.graphics.Bitmap
- */
- Bitmap getAlbumArtForClient(String clientName, int maxWidth, int maxHeight);
-}
diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl
new file mode 100644
index 0000000..19ea202
--- /dev/null
+++ b/media/java/android/media/IRemoteControlDisplay.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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 android.media;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+
+/**
+ * @hide
+ * Interface registered through AudioManager of an object that displays information
+ * received from a remote control client.
+ * {@see AudioManager#registerRemoteControlDisplay(IRemoteControlDisplay)}.
+ */
+oneway interface IRemoteControlDisplay
+{
+ /**
+ * Sets the generation counter of the current client that is displayed on the remote control.
+ */
+ void setCurrentClientGenerationId(int clientGeneration);
+
+ void setPlaybackState(int generationId, int state);
+
+ void setMetadata(int generationId, in Bundle metadata);
+
+ void setTransportControlFlags(int generationId, int transportControlFlags);
+
+ void setArtwork(int generationId, in Bitmap artwork);
+}
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index c384636..bfe08b9 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -17,69 +17,84 @@
package android.media;
import android.content.ComponentName;
+import android.content.SharedPreferences.Editor;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
/**
* @hide
- * Interface for an object that exposes information meant to be consumed by remote controls
+ * CANDIDATE FOR SDK
+ * RemoteControlClient enables exposing information meant to be consumed by remote controls
* capable of displaying metadata, album art and media transport control buttons.
- * Such a remote control client object is associated with a media button event receiver
+ * A remote control client object is associated with a media button event receiver
* when registered through
- * {@link AudioManager#registerRemoteControlClient(ComponentName, RemoteControlClient)}.
+ * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
*/
-public interface RemoteControlClient
+public class RemoteControlClient
{
+ private final static String TAG = "RemoteControlClient";
+
/**
* Playback state of a RemoteControlClient which is stopped.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_STOPPED = 1;
/**
* Playback state of a RemoteControlClient which is paused.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_PAUSED = 2;
/**
* Playback state of a RemoteControlClient which is playing media.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_PLAYING = 3;
/**
* Playback state of a RemoteControlClient which is fast forwarding in the media
* it is currently playing.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_FAST_FORWARDING = 4;
/**
* Playback state of a RemoteControlClient which is fast rewinding in the media
* it is currently playing.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_REWINDING = 5;
/**
* Playback state of a RemoteControlClient which is skipping to the next
* logical chapter (such as a song in a playlist) in the media it is currently playing.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_SKIPPING_FORWARDS = 6;
/**
* Playback state of a RemoteControlClient which is skipping back to the previous
* logical chapter (such as a song in a playlist) in the media it is currently playing.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
/**
* Playback state of a RemoteControlClient which is buffering data to play before it can
* start or resume playback.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_BUFFERING = 8;
/**
@@ -88,98 +103,188 @@ public interface RemoteControlClient
* connectivity when attempting to stream data from a server, or expired user credentials
* when trying to play subscription-based content.
*
- * @see android.media.RemoteControlClient#getPlaybackState()
+ * @see #setPlaybackState(int)
*/
public final static int PLAYSTATE_ERROR = 9;
+ /**
+ * @hide
+ * The value of a playback state when none has been declared
+ */
+ public final static int PLAYSTATE_NONE = 0;
/**
* Flag indicating a RemoteControlClient makes use of the "previous" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS
*/
public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
/**
* Flag indicating a RemoteControlClient makes use of the "rewing" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND
*/
public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
/**
* Flag indicating a RemoteControlClient makes use of the "play" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY
*/
public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
/**
* Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE
*/
public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
/**
* Flag indicating a RemoteControlClient makes use of the "pause" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE
*/
public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
/**
* Flag indicating a RemoteControlClient makes use of the "stop" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_STOP
*/
public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
/**
* Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD
*/
public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
/**
* Flag indicating a RemoteControlClient makes use of the "next" media key.
*
- * @see android.media.RemoteControlClient#getTransportControlFlags()
+ * @see #setTransportControlFlags(int)
* @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
*/
public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
/**
- * Flag used to signal that the metadata exposed by the RemoteControlClient has changed.
- *
- * @see #notifyRemoteControlInformationChanged(ComponentName, int)
+ * @hide
+ * The flags for when no media keys are declared supported
+ */
+ public final static int FLAGS_KEY_MEDIA_NONE = 0;
+
+ /**
+ * @hide
+ * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
*/
- public final static int FLAG_INFORMATION_CHANGED_METADATA = 1 << 0;
+ public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
/**
+ * @hide
+ * FIXME doc not valid
* Flag used to signal that the transport control buttons supported by the
* RemoteControlClient have changed.
* This can for instance happen when playback is at the end of a playlist, and the "next"
* operation is not supported anymore.
- *
- * @see #notifyRemoteControlInformationChanged(ComponentName, int)
*/
- public final static int FLAG_INFORMATION_CHANGED_KEY_MEDIA = 1 << 1;
+ public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
/**
+ * @hide
+ * FIXME doc not valid
* Flag used to signal that the playback state of the RemoteControlClient has changed.
- *
- * @see #notifyRemoteControlInformationChanged(ComponentName, int)
*/
- public final static int FLAG_INFORMATION_CHANGED_PLAYSTATE = 1 << 2;
+ public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
/**
+ * @hide
+ * FIXME doc not valid
* Flag used to signal that the album art for the RemoteControlClient has changed.
- *
- * @see #notifyRemoteControlInformationChanged(ComponentName, int)
*/
- public final static int FLAG_INFORMATION_CHANGED_ALBUM_ART = 1 << 3;
+ public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
+
+ /**
+ * Class constructor.
+ * @param mediaButtonEventReceiver the receiver for the media button events.
+ * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
+ * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
+ */
+ public RemoteControlClient(ComponentName mediaButtonEventReceiver) {
+ mRcEventReceiver = mediaButtonEventReceiver;
+
+ Looper looper;
+ if ((looper = Looper.myLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else {
+ mEventHandler = null;
+ Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
+ }
+ }
+
+ /**
+ * Class constructor for a remote control client whose internal event handling
+ * happens on a user-provided Looper.
+ * @param mediaButtonEventReceiver the receiver for the media button events.
+ * @param looper the Looper running the event loop.
+ * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
+ * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
+ */
+ public RemoteControlClient(ComponentName mediaButtonEventReceiver, Looper looper) {
+ mRcEventReceiver = mediaButtonEventReceiver;
+
+ mEventHandler = new EventHandler(this, looper);
+ }
+
+ /**
+ * Class used to modify metadata in a {@link RemoteControlClient} object.
+ */
+ public class MetadataEditor {
+
+ private MetadataEditor() { /* only use factory */ }
+
+ public MetadataEditor putString(int key, String value) {
+ return this;
+ }
+
+ public MetadataEditor putBitmap(int key, Bitmap bitmap) {
+ return this;
+ }
+
+ public void clear() {
+
+ }
+
+ public void apply() {
+
+ }
+ }
+
+ public MetadataEditor editMetadata(boolean startEmpty) {
+ return (new MetadataEditor());
+ }
+
+
+ /**
+ * @hide
+ * FIXME migrate this functionality under MetadataEditor
+ * Start collecting information to be displayed.
+ * Use {@link #commitMetadata()} to signal the end of the collection which has been created
+ * through one or multiple calls to {@link #addMetadataString(int, int, String)}.
+ */
+ public void startMetadata() {
+ synchronized(mCacheLock) {
+ mMetadata.clear();
+ }
+ }
/**
- * Called by a remote control to retrieve a String of information to display.
- * @param field the identifier for a metadata field to retrieve. Valid values are
+ * @hide
+ * FIXME migrate this functionality under MetadataEditor
+ * Adds textual information to be displayed.
+ * Note that none of the information added before {@link #startMetadata()},
+ * and after {@link #commitMetadata()} 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},
@@ -195,14 +300,54 @@ public interface RemoteControlClient
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
- * @return null if the requested field is not supported, or the String matching the
- * metadata field.
+ * @param value the String for the field value, or null to signify there is no valid
+ * information for the field.
*/
- String getMetadataString(int field);
+ public void addMetadataString(int key, String value) {
+ synchronized(mCacheLock) {
+ // store locally
+ mMetadata.putString(String.valueOf(key), value);
+ }
+ }
/**
- * Called by a remote control to retrieve the current playback state.
- * @return one of the following values:
+ * @hide
+ * FIXME migrate this functionality under MetadataEditor
+ * Marks all the metadata previously set with {@link #addMetadataString(int, int, String)} as
+ * eligible to be displayed.
+ */
+ public void commitMetadata() {
+ synchronized(mCacheLock) {
+ // send to remote control display if conditions are met
+ sendMetadata_syncCacheLock();
+ }
+ }
+
+ /**
+ * @hide
+ * FIXME migrate this functionality under MetadataEditor
+ * Sets the album / artwork picture to be displayed on the remote control.
+ * @param artwork the bitmap for the artwork, or null if there isn't any.
+ * @see android.graphics.Bitmap
+ */
+ public void setArtwork(Bitmap artwork) {
+ synchronized(mCacheLock) {
+ // resize and store locally
+ if (mArtworkExpectedWidth > 0) {
+ mArtwork = scaleBitmapIfTooBig(artwork,
+ mArtworkExpectedWidth, mArtworkExpectedHeight);
+ } else {
+ // no valid resize dimensions, store as is
+ mArtwork = artwork;
+ }
+ // send to remote control display if conditions are met
+ sendArtwork_syncCacheLock();
+ }
+ }
+
+ /**
+ * Sets the current playback state.
+ * @param state the current playback state, one of the following values:
* {@link #PLAYSTATE_STOPPED},
* {@link #PLAYSTATE_PAUSED},
* {@link #PLAYSTATE_PLAYING},
@@ -213,12 +358,20 @@ public interface RemoteControlClient
* {@link #PLAYSTATE_BUFFERING},
* {@link #PLAYSTATE_ERROR}.
*/
- int getPlaybackState();
+ public void setPlaybackState(int state) {
+ synchronized(mCacheLock) {
+ // store locally
+ mPlaybackState = state;
+
+ // send to remote control display if conditions are met
+ sendPlaybackState_syncCacheLock();
+ }
+ }
/**
- * Called by a remote control to retrieve the flags for the media transport control buttons
- * that this client supports.
- * @see {@link #FLAG_KEY_MEDIA_PREVIOUS},
+ * Sets the flags for the media transport control buttons that this client supports.
+ * @param a combination of the following flags:
+ * {@link #FLAG_KEY_MEDIA_PREVIOUS},
* {@link #FLAG_KEY_MEDIA_REWIND},
* {@link #FLAG_KEY_MEDIA_PLAY},
* {@link #FLAG_KEY_MEDIA_PLAY_PAUSE},
@@ -227,17 +380,311 @@ public interface RemoteControlClient
* {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
* {@link #FLAG_KEY_MEDIA_NEXT}
*/
- int getTransportControlFlags();
+ public void setTransportControlFlags(int transportControlFlags) {
+ synchronized(mCacheLock) {
+ // store locally
+ mTransportControlFlags = transportControlFlags;
+
+ // send to remote control display if conditions are met
+ sendTransportControlFlags_syncCacheLock();
+ }
+ }
/**
- * Called by a remote control to retrieve the album art picture at the requested size.
- * Note that returning a bitmap smaller than the maximum requested dimension is accepted
- * and it will be scaled as needed, but exceeding the maximum dimensions may produce
- * unspecified results, such as the image being cropped or simply not being displayed.
- * @param maxWidth the maximum width of the requested bitmap expressed in pixels.
- * @param maxHeight the maximum height of the requested bitmap expressed in pixels.
- * @return the bitmap for the album art, or null if there isn't any.
- * @see android.graphics.Bitmap
+ * Lock for all cached data
+ */
+ private final Object mCacheLock = new Object();
+ /**
+ * Cache for the playback state.
+ * Access synchronized on mCacheLock
*/
- Bitmap getAlbumArt(int maxWidth, int maxHeight);
+ private int mPlaybackState = PLAYSTATE_NONE;
+ /**
+ * Cache for the artwork bitmap.
+ * Access synchronized on mCacheLock
+ */
+ private Bitmap mArtwork;
+ private final int ARTWORK_DEFAULT_SIZE = 256;
+ private int mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
+ private int mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
+ /**
+ * Cache for the transport control mask.
+ * Access synchronized on mCacheLock
+ */
+ private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
+ /**
+ * Cache for the metadata strings.
+ * Access synchronized on mCacheLock
+ */
+ private Bundle mMetadata = new Bundle();
+ /**
+ * The current remote control client generation ID across the system
+ */
+ private int mCurrentClientGenId = -1;
+ /**
+ * The remote control client generation ID, the last time it was told it was the current RC.
+ * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control
+ * client is the "focused" one, and that whenever this client's info is updated, it needs to
+ * send it to the known IRemoteControlDisplay interfaces.
+ */
+ private int mInternalClientGenId = -2;
+
+ /**
+ * The media button event receiver associated with this remote control client
+ */
+ private final ComponentName mRcEventReceiver;
+
+ /**
+ * The remote control display to which this client will send information.
+ * NOTE: Only one IRemoteControlDisplay supported in this implementation
+ */
+ private IRemoteControlDisplay mRcDisplay;
+
+ /**
+ * @hide
+ * Accessor to media button event receiver
+ */
+ public ComponentName getRcEventReceiver() {
+ return mRcEventReceiver;
+ }
+ /**
+ * @hide
+ * Accessor to IRemoteControlClient
+ */
+ public IRemoteControlClient getIRemoteControlClient() {
+ return mIRCC;
+ }
+
+ /**
+ * The IRemoteControlClient implementation
+ */
+ private IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
+
+ public void onInformationRequested(int clientGeneration, int infoFlags,
+ int artWidth, int artHeight) {
+ // only post messages, we can't block here
+ if (mEventHandler != null) {
+ // signal new client
+ mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
+ mEventHandler.dispatchMessage(
+ mEventHandler.obtainMessage(
+ MSG_NEW_INTERNAL_CLIENT_GEN,
+ artWidth, artHeight,
+ new Integer(clientGeneration)));
+ // send the information
+ mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
+ mEventHandler.removeMessages(MSG_REQUEST_METADATA);
+ mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL);
+ mEventHandler.removeMessages(MSG_REQUEST_ARTWORK);
+ mEventHandler.dispatchMessage(
+ mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE));
+ mEventHandler.dispatchMessage(
+ mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL));
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA));
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK));
+ }
+ }
+
+ public void setCurrentClientGenerationId(int clientGeneration) {
+ // only post messages, we can't block here
+ if (mEventHandler != null) {
+ mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN);
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+ MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/));
+ }
+ }
+
+ public void plugRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ // only post messages, we can't block here
+ if (mEventHandler != null) {
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+ MSG_PLUG_DISPLAY, rcd));
+ }
+ }
+
+ public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ // only post messages, we can't block here
+ if (mEventHandler != null) {
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+ MSG_UNPLUG_DISPLAY, rcd));
+ }
+ }
+ };
+
+ private EventHandler mEventHandler;
+ private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
+ private final static int MSG_REQUEST_METADATA = 2;
+ private final static int MSG_REQUEST_TRANSPORTCONTROL = 3;
+ private final static int MSG_REQUEST_ARTWORK = 4;
+ private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5;
+ private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
+ private final static int MSG_PLUG_DISPLAY = 7;
+ private final static int MSG_UNPLUG_DISPLAY = 8;
+
+ private class EventHandler extends Handler {
+ public EventHandler(RemoteControlClient rcc, Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_REQUEST_PLAYBACK_STATE:
+ synchronized (mCacheLock) {
+ sendPlaybackState_syncCacheLock();
+ }
+ break;
+ case MSG_REQUEST_METADATA:
+ synchronized (mCacheLock) {
+ sendMetadata_syncCacheLock();
+ }
+ break;
+ case MSG_REQUEST_TRANSPORTCONTROL:
+ synchronized (mCacheLock) {
+ sendTransportControlFlags_syncCacheLock();
+ }
+ break;
+ case MSG_REQUEST_ARTWORK:
+ synchronized (mCacheLock) {
+ sendArtwork_syncCacheLock();
+ }
+ break;
+ case MSG_NEW_INTERNAL_CLIENT_GEN:
+ onNewInternalClientGen((Integer)msg.obj, msg.arg1, msg.arg2);
+ break;
+ case MSG_NEW_CURRENT_CLIENT_GEN:
+ onNewCurrentClientGen(msg.arg1);
+ break;
+ case MSG_PLUG_DISPLAY:
+ onPlugDisplay((IRemoteControlDisplay)msg.obj);
+ break;
+ case MSG_UNPLUG_DISPLAY:
+ onUnplugDisplay((IRemoteControlDisplay)msg.obj);
+ break;
+ default:
+ Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
+ }
+ }
+ }
+
+ private void sendPlaybackState_syncCacheLock() {
+ if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
+ try {
+ mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in setPlaybackState(), dead display "+e);
+ mRcDisplay = null;
+ mArtworkExpectedWidth = -1;
+ mArtworkExpectedHeight = -1;
+ }
+ }
+ }
+
+ private void sendMetadata_syncCacheLock() {
+ if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
+ try {
+ mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in sendPlaybackState(), dead display "+e);
+ mRcDisplay = null;
+ mArtworkExpectedWidth = -1;
+ mArtworkExpectedHeight = -1;
+ }
+ }
+ }
+
+ private void sendTransportControlFlags_syncCacheLock() {
+ if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
+ try {
+ mRcDisplay.setTransportControlFlags(mInternalClientGenId,
+ mTransportControlFlags);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in sendTransportControlFlags(), dead display "+e);
+ mRcDisplay = null;
+ mArtworkExpectedWidth = -1;
+ mArtworkExpectedHeight = -1;
+ }
+ }
+ }
+
+ private void sendArtwork_syncCacheLock() {
+ if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
+ // even though we have already scaled in setArtwork(), when this client needs to
+ // send the bitmap, there might be newer and smaller expected dimensions, so we have
+ // to check again.
+ mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
+ try {
+ mRcDisplay.setArtwork(mInternalClientGenId, mArtwork);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in sendArtwork(), dead display "+e);
+ mRcDisplay = null;
+ mArtworkExpectedWidth = -1;
+ mArtworkExpectedHeight = -1;
+ }
+ }
+ }
+
+ private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) {
+ synchronized (mCacheLock) {
+ // this remote control client is told it is the "focused" one:
+ // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
+ mInternalClientGenId = clientGeneration.intValue();
+ if (artWidth > 0) {
+ mArtworkExpectedWidth = artWidth;
+ mArtworkExpectedHeight = artHeight;
+ }
+ }
+ }
+
+ private void onNewCurrentClientGen(int clientGeneration) {
+ synchronized (mCacheLock) {
+ mCurrentClientGenId = clientGeneration;
+ }
+ }
+
+ private void onPlugDisplay(IRemoteControlDisplay rcd) {
+ synchronized(mCacheLock) {
+ mRcDisplay = rcd;
+ }
+ }
+
+ private void onUnplugDisplay(IRemoteControlDisplay rcd) {
+ synchronized(mCacheLock) {
+ if ((mRcDisplay != null) && (mRcDisplay.equals(rcd))) {
+ mRcDisplay = null;
+ mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
+ mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
+ }
+ }
+ }
+
+ /**
+ * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap.
+ * If the bitmap fits, then do nothing and return the original.
+ *
+ * @param bitmap
+ * @param maxWidth
+ * @param maxHeight
+ * @return
+ */
+
+ private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
+ final int width = bitmap.getWidth();
+ final int height = bitmap.getHeight();
+ if (width > maxWidth || height > maxHeight) {
+ float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
+ int newWidth = Math.round(scale * width);
+ int newHeight = Math.round(scale * height);
+ Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, bitmap.getConfig());
+ Canvas canvas = new Canvas(outBitmap);
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setFilterBitmap(true);
+ canvas.drawBitmap(bitmap, null,
+ new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
+ bitmap = outBitmap;
+ }
+ return bitmap;
+
+ }
}