am 7c8aeadd: Merge "Support multiple IRemoteControlDisplay" into jb-mr2-dev

* commit '7c8aeadd9a010b79c04261f0a9092942d1db0dc2':
  Support multiple IRemoteControlDisplay
This commit is contained in:
Jean-Michel Trivi
2013-03-12 16:43:47 +00:00
committed by Android Git Automerger
5 changed files with 431 additions and 259 deletions

View File

@@ -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

View File

@@ -4905,7 +4905,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
" -- vol: " + rcse.mPlaybackVolume +
" -- volMax: " + rcse.mPlaybackVolumeMax +
" -- volObs: " + rcse.mRemoteVolumeObs);
}
}
synchronized (mMainRemote) {
@@ -4921,6 +4920,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<DisplayInfoForServer> 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
@@ -5059,16 +5075,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<DisplayInfoForServer> 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();
}
}
}
}
}
@@ -5086,7 +5106,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();
}
@@ -5144,8 +5164,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;
@@ -5409,13 +5428,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();
@@ -5485,102 +5500,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<DisplayInfoForServer> 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<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1);
/**
* Plug each registered display into the specified client
* @param rcc, guaranteed non null
*/
private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) {
final Iterator<DisplayInfoForServer> 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<DisplayInfoForServer> 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<RemoteControlStackEntry> 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);
}
}
}
@@ -5593,44 +5647,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<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();
boolean displayWasPluggedIn = false;
final Iterator<DisplayInfoForServer> 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<RemoteControlStackEntry> 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<DisplayInfoForServer> 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<RemoteControlStackEntry> 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);
}
}
}
}
}
}
@@ -6233,6 +6329,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:");

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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<DisplayInfoForClient> mRcDisplays = new ArrayList<DisplayInfoForClient>(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<DisplayInfoForClient> 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<DisplayInfoForClient> 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<DisplayInfoForClient> 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<DisplayInfoForClient> 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<DisplayInfoForClient> 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<DisplayInfoForClient> 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<DisplayInfoForClient> 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<DisplayInfoForClient> 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;
}
}
}
}