diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 86976b8b5164e..135d2c833e23e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2171,15 +2171,36 @@ public class AudioManager { /** * @hide * Registers a remote control display that will be sent information by remote control clients. - * @param rcd + * Use this method if your IRemoteControlDisplay is not going to display artwork, otherwise + * use {@link #registerRemoteControlDisplay(IRemoteControlDisplay, int, int)} to pass the + * artwork size directly, or + * {@link #remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay, int, int)} later if artwork + * is not yet needed. + * @param rcd the IRemoteControlDisplay */ public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) { + // passing a negative value for art work width and height as they are unknown at this stage + registerRemoteControlDisplay(rcd, /*w*/-1, /*h*/ -1); + } + + /** + * @hide + * Registers a remote control display that will be sent information by remote control clients. + * @param rcd + * @param w the maximum width of the expected bitmap. Negative values indicate it is + * useless to send artwork. + * @param h the maximum height of the expected bitmap. Negative values indicate it is + * useless to send artwork. + */ + public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { if (rcd == null) { return; } IAudioService service = getService(); try { - service.registerRemoteControlDisplay(rcd); + // passing a negative value for art work width and height as they are unknown at + // this stage + service.registerRemoteControlDisplay(rcd, w, h); } catch (RemoteException e) { Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e); } @@ -2223,63 +2244,6 @@ public class AudioManager { } } - // 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 - * should be updated because a new remote control client is now active. If there is no - * {@link #EXTRA_REMOTE_CONTROL_CLIENT}, the remote control display should be cleared - * because there is no valid client to supply it with information. - * - * @see #EXTRA_REMOTE_CONTROL_CLIENT - */ - 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. - * - * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION - */ - 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. - * This String is passed as the client name when calling methods from the - * IRemoteControlClientDispatcher interface. - * - * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION - */ - 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. - * The {@link android.content.ComponentName} value of the event receiver can be retrieved with - * {@link android.content.ComponentName#unflattenFromString(String)} - * - * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION - */ - 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. - * - * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION - */ - public static final String EXTRA_REMOTE_CONTROL_CLIENT_INFO_CHANGED = - "android.media.EXTRA_REMOTE_CONTROL_CLIENT_INFO_CHANGED"; - /** * @hide * Reload audio settings. This method is called by Settings backup diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index ed2a8dafdf2a2..6bc239e71cb25 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -4890,7 +4890,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { " -- vol: " + rcse.mPlaybackVolume + " -- volMax: " + rcse.mPlaybackVolumeMax + " -- volObs: " + rcse.mRemoteVolumeObs); - } } synchronized (mMainRemote) { @@ -4906,6 +4905,23 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + /** + * Helper function: + * Display in the log the current entries in the list of remote control displays + */ + private void dumpRCDList(PrintWriter pw) { + pw.println("\nRemote Control Display list entries:"); + synchronized(mRCStack) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + pw.println(" IRCD: " + di.mRcDisplay + + " -- w:" + di.mArtworkExpectedWidth + + " -- h:" + di.mArtworkExpectedHeight); + } + } + } + /** * Helper function: * Remove any entry in the remote control stack that has the same package name as packageName @@ -5044,16 +5060,20 @@ public class AudioService extends IAudioService.Stub implements OnFinished { */ private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, PendingIntent newMediaIntent, boolean clearing) { - // NOTE: Only one IRemoteControlDisplay supported in this implementation - if (mRcDisplay != null) { - try { - mRcDisplay.setCurrentClientId( - newClientGeneration, newMediaIntent, clearing); - } catch (RemoteException e) { - Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc() "+e); - // if we had a display before, stop monitoring its death - rcDisplay_stopDeathMonitor_syncRcStack(); - mRcDisplay = null; + synchronized(mRCStack) { + if (mRcDisplays.size() > 0) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = displayIterator.next(); + try { + di.mRcDisplay.setCurrentClientId( + newClientGeneration, newMediaIntent, clearing); + } catch (RemoteException e) { + Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e); + di.release(); + displayIterator.remove(); + } + } } } } @@ -5071,7 +5091,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { try { se.mRcClient.setCurrentClientGenerationId(newClientGeneration); } catch (RemoteException e) { - Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()"+e); + Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e); stackIterator.remove(); se.unlinkToRcClientDeath(); } @@ -5129,8 +5149,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // tell the current client that it needs to send info try { - mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, - flags, mArtworkExpectedWidth, mArtworkExpectedHeight); + mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags); } catch (RemoteException e) { Log.e(TAG, "Current valid remote client is dead: "+e); mCurrentRcClient = null; @@ -5394,13 +5413,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { rccId = rcse.mRccId; // there is a new (non-null) client: - // 1/ give the new client the current display (if any) - if (mRcDisplay != null) { - try { - rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay); - } catch (RemoteException e) { - Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e); - } + // 1/ give the new client the displays (if any) + if (mRcDisplays.size() > 0) { + plugRemoteControlDisplaysIntoClient_syncRcStack(rcse.mRcClient); } // 2/ monitor the new client's death IBinder b = rcse.mRcClient.asBinder(); @@ -5470,102 +5485,141 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } - /** - * 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 { - private IBinder mCb; // To be notified of client's death - public RcDisplayDeathHandler(IBinder b) { - if (DEBUG_RC) Log.i(TAG, "new RcDisplayDeathHandler for "+b); - mCb = b; + /** + * A class to encapsulate all the information about a remote control display. + * After instanciation, init() must always be called before the object is added in the list + * of displays. + * Before being removed from the list of displays, release() must always be called (otherwise + * it will leak death handlers). + */ + private class DisplayInfoForServer implements IBinder.DeathRecipient { + /** may never be null */ + private IRemoteControlDisplay mRcDisplay; + private IBinder mRcDisplayBinder; + private int mArtworkExpectedWidth = -1; + private int mArtworkExpectedHeight = -1; + + public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) { + if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h); + mRcDisplay = rcd; + mRcDisplayBinder = rcd.asBinder(); + mArtworkExpectedWidth = w; + mArtworkExpectedHeight = h; + } + + public boolean init() { + try { + mRcDisplayBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + // remote control display is DOA, disqualify it + Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder); + return false; + } + return true; + } + + public void release() { + try { + mRcDisplayBinder.unlinkToDeath(this, 0); + } catch (java.util.NoSuchElementException e) { + // not much we can do here, the display should have been unregistered anyway + Log.e(TAG, "Error in DisplaInfoForServer.relase()", e); + } } public void binderDied() { synchronized(mRCStack) { - Log.w(TAG, "RemoteControl: display died"); - mRcDisplay = null; + Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died"); + // remove the display from the list + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay == mRcDisplay) { + if (DEBUG_RC) Log.w(TAG, " RCD removed from list"); + displayIterator.remove(); + return; + } + } } } - - public void unlinkToRcDisplayDeath() { - if (DEBUG_RC) Log.i(TAG, "unlinkToRcDisplayDeath for "+mCb); - try { - mCb.unlinkToDeath(this, 0); - } catch (java.util.NoSuchElementException e) { - // not much we can do here, the display was being unregistered anyway - Log.e(TAG, "Encountered " + e + " in unlinkToRcDisplayDeath()"); - e.printStackTrace(); - } - } - - } - - private void rcDisplay_stopDeathMonitor_syncRcStack() { - if (mRcDisplay != null) { // implies (mRcDisplayDeathHandler != null) - // we had a display before, stop monitoring its death - mRcDisplayDeathHandler.unlinkToRcDisplayDeath(); - } } - private void rcDisplay_startDeathMonitor_syncRcStack() { - if (mRcDisplay != null) { - // new non-null display, monitor its death - IBinder b = mRcDisplay.asBinder(); - mRcDisplayDeathHandler = new RcDisplayDeathHandler(b); + /** + * The remote control displays. + * Access synchronized on mRCStack + */ + private ArrayList mRcDisplays = new ArrayList(1); + + /** + * Plug each registered display into the specified client + * @param rcc, guaranteed non null + */ + private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); try { - b.linkToDeath(mRcDisplayDeathHandler, 0); + rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth, + di.mArtworkExpectedHeight); } catch (RemoteException e) { - // remote control display is DOA, disqualify it - Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + b); - mRcDisplay = null; + Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e); } } } + /** + * Is the remote control display interface already registered + * @param rcd + * @return true if the IRemoteControlDisplay is already in the list of displays + */ + private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + return true; + } + } + return false; + } + /** * Register an IRemoteControlDisplay. * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient * at the top of the stack to update the new display with its information. - * Since only one IRemoteControlDisplay is supported, this will unregister the previous display. + * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int) * @param rcd the IRemoteControlDisplay to register. No effect if null. + * @param w the maximum width of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param h the maximum height of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. */ - public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) { + public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")"); synchronized(mAudioFocusLock) { synchronized(mRCStack) { - if ((mRcDisplay == rcd) || (rcd == null)) { + if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(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(); + DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h); + if (!di.init()) { + if (DEBUG_RC) Log.e(TAG, " error registering RCD"); + return; + } + // add RCD to list of displays + mRcDisplays.add(di); - // let all the remote control clients know there is a new display, so the remote - // control stack traversal order doesn't matter. - // No need to unplug the previous because we only support one display - // and the clients don't track the death of the display + // let all the remote control clients know there is a new display (so the remote + // control stack traversal order doesn't matter). Iterator stackIterator = mRCStack.iterator(); while(stackIterator.hasNext()) { RemoteControlStackEntry rcse = stackIterator.next(); if(rcse.mRcClient != null) { try { - rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay); + rcse.mRcClient.plugRemoteControlDisplay(rcd, w, h); } catch (RemoteException e) { - Log.e(TAG, "Error connecting remote control display to client: " + e); - e.printStackTrace(); + Log.e(TAG, "Error connecting RCD to client: ", e); } } } @@ -5578,44 +5632,86 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** * Unregister an IRemoteControlDisplay. - * Since only one IRemoteControlDisplay is supported, this has no effect if the one to - * unregister is not the current one. + * No effect if the IRemoteControlDisplay hasn't been successfully registered. + * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay) * @param rcd the IRemoteControlDisplay to unregister. No effect if null. */ public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")"); synchronized(mRCStack) { - // only one display here, so you can only unregister the current display - if ((rcd == null) || (rcd != mRcDisplay)) { - if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD"); + if (rcd == null) { return; } - // if we had a display before, stop monitoring its death - rcDisplay_stopDeathMonitor_syncRcStack(); - mRcDisplay = null; - // disconnect this remote control display from all the clients, so the remote - // control stack traversal order doesn't matter - Iterator 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(); + boolean displayWasPluggedIn = false; + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext() && !displayWasPluggedIn) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + displayWasPluggedIn = true; + di.release(); + displayIterator.remove(); + } + } + + if (displayWasPluggedIn) { + // disconnect this remote control display from all the clients, so the remote + // control stack traversal order doesn't matter + final Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + final 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); + } } } + } else { + if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD"); } } } + /** + * Update the size of the artwork used by an IRemoteControlDisplay. + * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int) + * @param rcd the IRemoteControlDisplay with the new artwork size requirement + * @param w the maximum width of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param h the maximum height of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + */ public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { synchronized(mRCStack) { - // NOTE: Only one IRemoteControlDisplay supported in this implementation - mArtworkExpectedWidth = w; - mArtworkExpectedHeight = h; + final Iterator displayIterator = mRcDisplays.iterator(); + boolean artworkSizeUpdate = false; + while (displayIterator.hasNext() && !artworkSizeUpdate) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { + di.mArtworkExpectedWidth = w; + di.mArtworkExpectedHeight = h; + artworkSizeUpdate = true; + } + } + } + if (artworkSizeUpdate) { + // RCD is currently plugged in and its artwork size has changed, notify all RCCs, + // stack traversal order doesn't matter + final Iterator stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + final RemoteControlStackEntry rcse = stackIterator.next(); + if(rcse.mRcClient != null) { + try { + rcse.mRcClient.setBitmapSizeForDisplay(rcd, w, h); + } catch (RemoteException e) { + Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e); + } + } + } + } } } @@ -6218,6 +6314,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { dumpFocusStack(pw); dumpRCStack(pw); dumpRCCStack(pw); + dumpRCDList(pw); dumpStreamStates(pw); dumpRingerMode(pw); pw.println("\nAudio routes:"); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index ea99069b0606c..312c25274824d 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -131,8 +131,31 @@ interface IAudioService { oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent, in IRemoteControlClient rcClient); - oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd); + /** + * Register an IRemoteControlDisplay. + * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient + * at the top of the stack to update the new display with its information. + * @param rcd the IRemoteControlDisplay to register. No effect if null. + * @param w the maximum width of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param h the maximum height of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + */ + oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h); + /** + * Unregister an IRemoteControlDisplay. + * No effect if the IRemoteControlDisplay hasn't been successfully registered. + * @param rcd the IRemoteControlDisplay to unregister. No effect if null. + */ oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd); + /** + * Update the size of the artwork used by an IRemoteControlDisplay. + * @param rcd the IRemoteControlDisplay with the new artwork size requirement + * @param w the maximum width of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param h the maximum height of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + */ oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h); oneway void setPlaybackInfoForRcc(int rccId, int what, int value); diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl index 0fbba2026ac04..5600263b197be 100644 --- a/media/java/android/media/IRemoteControlClient.aidl +++ b/media/java/android/media/IRemoteControlClient.aidl @@ -34,18 +34,17 @@ oneway interface IRemoteControlClient * 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); + void onInformationRequested(int generationId, int infoFlags); /** * Sets the generation counter of the current client that is displayed on the remote control. */ void setCurrentClientGenerationId(int clientGeneration); - void plugRemoteControlDisplay(IRemoteControlDisplay rcd); + void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h); void unplugRemoteControlDisplay(IRemoteControlDisplay rcd); + void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h); } \ No newline at end of file diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 4c71aced56c5a..9a0ecdfbd2876 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -36,6 +36,8 @@ import android.os.SystemClock; import android.util.Log; import java.lang.IllegalArgumentException; +import java.util.ArrayList; +import java.util.Iterator; /** * RemoteControlClient enables exposing information meant to be consumed by remote controls @@ -498,13 +500,7 @@ public class RemoteControlClient if (key != BITMAP_KEY_ARTWORK) { throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); } - if ((mArtworkExpectedWidth > 0) && (mArtworkExpectedHeight > 0)) { - mEditorArtwork = scaleBitmapIfTooBig(bitmap, - mArtworkExpectedWidth, mArtworkExpectedHeight); - } else { - // no valid resize dimensions, store as is - mEditorArtwork = bitmap; - } + mEditorArtwork = bitmap; mArtworkChanged = true; return this; } @@ -536,10 +532,10 @@ public class RemoteControlClient synchronized(mCacheLock) { // assign the edited data mMetadata = new Bundle(mEditorMetadata); - if ((mArtwork != null) && (!mArtwork.equals(mEditorArtwork))) { - mArtwork.recycle(); + if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) { + mOriginalArtwork.recycle(); } - mArtwork = mEditorArtwork; + mOriginalArtwork = mEditorArtwork; mEditorArtwork = null; if (mMetadataChanged & mArtworkChanged) { // send to remote control display if conditions are met @@ -571,7 +567,7 @@ public class RemoteControlClient editor.mArtworkChanged = true; } else { editor.mEditorMetadata = new Bundle(mMetadata); - editor.mEditorArtwork = mArtwork; + editor.mEditorArtwork = mOriginalArtwork; editor.mMetadataChanged = false; editor.mArtworkChanged = false; } @@ -766,11 +762,7 @@ public class RemoteControlClient * accessed to be resized, in which case a copy will be made. This would add overhead in * Bundle operations. */ - private Bitmap mArtwork; - private final int ARTWORK_DEFAULT_SIZE = 256; - private final int ARTWORK_INVALID_SIZE = -1; - private int mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE; - private int mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE; + private Bitmap mOriginalArtwork; /** * Cache for the transport control mask. * Access synchronized on mCacheLock @@ -802,10 +794,27 @@ public class RemoteControlClient private final PendingIntent mRcMediaIntent; /** - * The remote control display to which this client will send information. - * NOTE: Only one IRemoteControlDisplay supported in this implementation + * A class to encapsulate all the information about a remote control display. + * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay */ - private IRemoteControlDisplay mRcDisplay; + private class DisplayInfoForClient { + /** may never be null */ + private IRemoteControlDisplay mRcDisplay; + private int mArtworkExpectedWidth; + private int mArtworkExpectedHeight; + + DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) { + mRcDisplay = rcd; + mArtworkExpectedWidth = w; + mArtworkExpectedHeight = h; + } + } + + /** + * The list of remote control displays to which this client will send information. + * Accessed and modified synchronized on mCacheLock + */ + private ArrayList mRcDisplays = new ArrayList(1); /** * @hide @@ -827,17 +836,14 @@ public class RemoteControlClient */ private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { - public void onInformationRequested(int clientGeneration, int infoFlags, - int artWidth, int artHeight) { + public void onInformationRequested(int clientGeneration, int infoFlags) { // 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))); + mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN, + /*arg1*/ clientGeneration, /*arg2, ignored*/ 0)); // send the information mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE); mEventHandler.removeMessages(MSG_REQUEST_METADATA); @@ -861,21 +867,29 @@ public class RemoteControlClient } } - public void plugRemoteControlDisplay(IRemoteControlDisplay rcd) { + public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { // only post messages, we can't block here - if (mEventHandler != null) { + if ((mEventHandler != null) && (rcd != null)) { mEventHandler.dispatchMessage(mEventHandler.obtainMessage( - MSG_PLUG_DISPLAY, rcd)); + MSG_PLUG_DISPLAY, w, h, rcd)); } } public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) { // only post messages, we can't block here - if (mEventHandler != null) { + if ((mEventHandler != null) && (rcd != null)) { mEventHandler.dispatchMessage(mEventHandler.obtainMessage( MSG_UNPLUG_DISPLAY, rcd)); } } + + public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) { + // only post messages, we can't block here + if ((mEventHandler != null) && (rcd != null)) { + mEventHandler.dispatchMessage(mEventHandler.obtainMessage( + MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd)); + } + } }; /** @@ -915,6 +929,7 @@ public class RemoteControlClient 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 final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9; private class EventHandler extends Handler { public EventHandler(RemoteControlClient rcc, Looper looper) { @@ -945,17 +960,20 @@ public class RemoteControlClient } break; case MSG_NEW_INTERNAL_CLIENT_GEN: - onNewInternalClientGen((Integer)msg.obj, msg.arg1, msg.arg2); + onNewInternalClientGen(msg.arg1); break; case MSG_NEW_CURRENT_CLIENT_GEN: onNewCurrentClientGen(msg.arg1); break; case MSG_PLUG_DISPLAY: - onPlugDisplay((IRemoteControlDisplay)msg.obj); + onPlugDisplay((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); break; case MSG_UNPLUG_DISPLAY: onUnplugDisplay((IRemoteControlDisplay)msg.obj); break; + case MSG_UPDATE_DISPLAY_ARTWORK_SIZE: + onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); + break; default: Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); } @@ -963,75 +981,106 @@ public class RemoteControlClient } //=========================================================== - // Communication with IRemoteControlDisplay - - private void detachFromDisplay_syncCacheLock() { - mRcDisplay = null; - mArtworkExpectedWidth = ARTWORK_INVALID_SIZE; - mArtworkExpectedHeight = ARTWORK_INVALID_SIZE; - } + // Communication with the IRemoteControlDisplay (the displays known to the system) private void sendPlaybackState_syncCacheLock() { - if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) { - try { - mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState, - mPlaybackStateChangeTimeMs); - } catch (RemoteException e) { - Log.e(TAG, "Error in setPlaybackState(), dead display "+e); - detachFromDisplay_syncCacheLock(); + if (mCurrentClientGenId == mInternalClientGenId) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + try { + di.mRcDisplay.setPlaybackState(mInternalClientGenId, + mPlaybackState, mPlaybackStateChangeTimeMs); + } catch (RemoteException e) { + Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); + displayIterator.remove(); + } } } } 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); - detachFromDisplay_syncCacheLock(); + if (mCurrentClientGenId == mInternalClientGenId) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + try { + di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); + } catch (RemoteException e) { + Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e); + displayIterator.remove(); + } } } } 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); - detachFromDisplay_syncCacheLock(); + if (mCurrentClientGenId == mInternalClientGenId) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + try { + di.mRcDisplay.setTransportControlFlags(mInternalClientGenId, + mTransportControlFlags); + } catch (RemoteException e) { + Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, + e); + displayIterator.remove(); + } } } } 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); - detachFromDisplay_syncCacheLock(); + // FIXME modify to cache all requested sizes? + if (mCurrentClientGenId == mInternalClientGenId) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) { + displayIterator.remove(); + } } } } - private void sendMetadataWithArtwork_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); + /** + * Send artwork to an IRemoteControlDisplay. + * @param di encapsulates the IRemoteControlDisplay that will receive the artwork, and its + * dimension requirements. + * @return false if there was an error communicating with the IRemoteControlDisplay. + */ + private boolean sendArtworkToDisplay(DisplayInfoForClient di) { + if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { + Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, + di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); try { - mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, mArtwork); + di.mRcDisplay.setArtwork(mInternalClientGenId, artwork); } catch (RemoteException e) { - Log.e(TAG, "Error in setAllMetadata(), dead display "+e); - detachFromDisplay_syncCacheLock(); + Log.e(TAG, "Error in sendArtworkToDisplay(), dead display " + di.mRcDisplay, e); + return false; + } + } + return true; + } + + private void sendMetadataWithArtwork_syncCacheLock() { + // FIXME modify to cache all requested sizes? + if (mCurrentClientGenId == mInternalClientGenId) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + try { + if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { + Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, + di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); + di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork); + } else { + di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); + } + } catch (RemoteException e) { + Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e); + displayIterator.remove(); + } } } } @@ -1067,15 +1116,11 @@ public class RemoteControlClient //=========================================================== // Message handlers - private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) { + private void onNewInternalClientGen(int clientGeneration) { 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; - } + mInternalClientGenId = clientGeneration; } } @@ -1085,18 +1130,62 @@ public class RemoteControlClient } } - private void onPlugDisplay(IRemoteControlDisplay rcd) { + /** pre-condition rcd != null */ + private void onPlugDisplay(IRemoteControlDisplay rcd, int w, int h) { synchronized(mCacheLock) { - mRcDisplay = rcd; + // do we have this display already? + boolean displayKnown = false; + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext() && !displayKnown) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder()); + if (displayKnown) { + // this display was known but the change in artwork size will cause the + // artwork to be refreshed + if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { + di.mArtworkExpectedWidth = w; + di.mArtworkExpectedHeight = h; + if (!sendArtworkToDisplay(di)) { + displayIterator.remove(); + } + } + } + } + if (!displayKnown) { + mRcDisplays.add(new DisplayInfoForClient(rcd, w, h)); + } } } + /** pre-condition rcd != null */ private void onUnplugDisplay(IRemoteControlDisplay rcd) { synchronized(mCacheLock) { - if ((mRcDisplay != null) && (mRcDisplay.asBinder().equals(rcd.asBinder()))) { - mRcDisplay = null; - mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE; - mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE; + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + displayIterator.remove(); + return; + } + } + } + } + + /** pre-condition rcd != null */ + private void onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h) { + synchronized(mCacheLock) { + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) && + ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) { + di.mArtworkExpectedWidth = w; + di.mArtworkExpectedHeight = h; + if (!sendArtworkToDisplay(di)) { + displayIterator.remove(); + } + break; + } } } }