summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorJean-Michel Trivi <jmtrivi@google.com>2012-06-11 15:03:52 -0700
committerJean-Michel Trivi <jmtrivi@google.com>2012-06-18 18:37:17 -0700
commit3114ce3861f20f9a5c2c59dd2629197a1f4874a8 (patch)
treec23f8f5c493fecd904022333cac099451fb20d6b /media
parent4cb3b76caa004867bac43f0001072e24bfa8c120 (diff)
downloadframeworks_base-3114ce3861f20f9a5c2c59dd2629197a1f4874a8.zip
frameworks_base-3114ce3861f20f9a5c2c59dd2629197a1f4874a8.tar.gz
frameworks_base-3114ce3861f20f9a5c2c59dd2629197a1f4874a8.tar.bz2
Remote volume handling
Extend RemoteControlClient class to enable an applicaton to specify more information about how it's playing media, now covering usecases where media playback happens "remotely". This playback information can be used to set the volume and maximum volume used remotely. Declare a new intent and associated extras in Intent, ACTION_VOLUME_UPDATE, so an application can be notified that the volume it handles should be updated. It can then use the new RemoteControlClient.setPlaybackInformation() method to notify AudioService what the volume is. Extend AudioService to maintain playback information associated with the RemoteControlClient information in the stack of media button event receivers (mRCStack). The information about the active remote is cached so the stack doesn't have to be iterated over in order to retrieve remote playback info. Events to "adjust" the remote volume based on hardware key presses cause the client application to be notified of volume updates, and the volume panel to display the volume set by the app. Revise which stream type is controlled when none is specified according to latest guidelines for remote playback. Update VolumePanel class to support a new pseudo stream type, AudioService.STREAM_REMOTE_MUSIC, that corresponds to the remote playback volume, and uses the new "media route" icon. Enable it to receive asynchronously new volume values for the remote that will be displayed if the UI is still up, and ignored otherwise. Now supports hiding/showing sliders dynamically so remote volume only appears when AudioService has a remote control client handling remote volume. Define new java symbols for the two media route icons. Modify lockscreen behavior: don't automatically control music volume when music is active, consider also remote playback. Still to do: - playback information set by RemoteControlClient should post a message for AudioService to update playback information instead of updating it synchronously Change-Id: I557aa687239f9acfe33a609f05876c67fa7eb967
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AudioManager.java31
-rw-r--r--media/java/android/media/AudioService.java464
-rw-r--r--media/java/android/media/IAudioService.aidl10
-rw-r--r--media/java/android/media/RemoteControlClient.java261
4 files changed, 733 insertions, 33 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e88c535..b6e4659 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -98,7 +98,10 @@ public class AudioManager {
/**
* @hide Broadcast intent when the volume for a particular stream type changes.
- * Includes the stream, the new volume and previous volumes
+ * Includes the stream, the new volume and previous volumes.
+ * Notes:
+ * - for internal platform use only, do not make public,
+ * - never used for "remote" volume changes
*
* @see #EXTRA_VOLUME_STREAM_TYPE
* @see #EXTRA_VOLUME_STREAM_VALUE
@@ -1498,6 +1501,24 @@ public class AudioManager {
return AudioSystem.isStreamActive(STREAM_MUSIC, 0);
}
+ /**
+ * @hide
+ * If the stream is active locally or remotely, adjust its volume according to the enforced
+ * priority rules.
+ * Note: only AudioManager.STREAM_MUSIC is supported at the moment
+ */
+ public void adjustLocalOrRemoteStreamVolume(int streamType, int direction) {
+ if (streamType != STREAM_MUSIC) {
+ Log.w(TAG, "adjustLocalOrRemoteStreamVolume() doesn't support stream " + streamType);
+ }
+ IAudioService service = getService();
+ try {
+ service.adjustLocalOrRemoteStreamVolume(streamType, direction);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in adjustLocalOrRemoteStreamVolume", e);
+ }
+ }
+
/*
* Sets a generic audio configuration parameter. The use of these parameters
* are platform dependant, see libaudio
@@ -2074,10 +2095,12 @@ public class AudioManager {
}
IAudioService service = getService();
try {
- service.registerRemoteControlClient(rcClient.getRcMediaIntent(), /* mediaIntent */
- rcClient.getIRemoteControlClient(), /* rcClient */
+ int rcseId = service.registerRemoteControlClient(
+ rcClient.getRcMediaIntent(), /* mediaIntent */
+ rcClient.getIRemoteControlClient(),/* rcClient */
// used to match media button event receiver and audio focus
- mContext.getPackageName()); /* packageName */
+ mContext.getPackageName()); /* packageName */
+ rcClient.setRcseId(rcseId);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerRemoteControlClient"+e);
}
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index ddb7e6b..b136c9d 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -101,6 +101,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
/** Debug remote control client/display feature */
protected static final boolean DEBUG_RC = false;
+ /** Debug volumes */
+ protected static final boolean DEBUG_VOL = false;
/** How long to delay before persisting a change in volume/ringer mode. */
private static final int PERSIST_DELAY = 500;
@@ -120,7 +122,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
/** If the msg is already queued, queue this one and leave the old. */
private static final int SENDMSG_QUEUE = 2;
- // AudioHandler message.whats
+ // AudioHandler messages
private static final int MSG_SET_DEVICE_VOLUME = 0;
private static final int MSG_PERSIST_VOLUME = 1;
private static final int MSG_PERSIST_MASTER_VOLUME = 2;
@@ -138,11 +140,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
private static final int MSG_SET_ALL_VOLUMES = 14;
private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 15;
private static final int MSG_REPORT_NEW_ROUTES = 16;
- // messages handled under wakelock, can only be queued, i.e. sent with queueMsgUnderWakeLock(),
+ private static final int MSG_REEVALUATE_REMOTE = 17;
+ // start of messages handled under wakelock
+ // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
- private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 17;
- private static final int MSG_SET_A2DP_CONNECTION_STATE = 18;
-
+ private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 18;
+ private static final int MSG_SET_A2DP_CONNECTION_STATE = 19;
+ // end of messages handled under wakelock
// flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be
// persisted
@@ -405,6 +409,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers
= new RemoteCallbackList<IAudioRoutesObserver>();
+ /**
+ * A fake stream type to match the notion of remote media playback
+ */
+ public final static int STREAM_REMOTE_MUSIC = -200;
+
///////////////////////////////////////////////////////////////////////////
// Construction
///////////////////////////////////////////////////////////////////////////
@@ -488,6 +497,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
mMasterVolumeRamp = context.getResources().getIntArray(
com.android.internal.R.array.config_masterVolumeRamp);
+ mMainRemote = new RemotePlaybackState(-1, MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC],
+ MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC]);
+ mHasRemotePlayback = false;
+ mMainRemoteIsActive = false;
+ postReevaluateRemote();
}
private void createAudioSystemThread() {
@@ -657,9 +671,20 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
adjustSuggestedStreamVolume(direction, AudioManager.USE_DEFAULT_STREAM_TYPE, flags);
}
+ /** @see AudioManager#adjustLocalOrRemoteStreamVolume(int, int) with current assumption
+ * on streamType: fixed to STREAM_MUSIC */
+ public void adjustLocalOrRemoteStreamVolume(int streamType, int direction) {
+ if (DEBUG_VOL) Log.d(TAG, "adjustLocalOrRemoteStreamVolume(dir="+direction+")");
+ if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
+ adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, 0);
+ } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+ adjustStreamVolume(AudioSystem.STREAM_MUSIC, direction, 0);
+ }
+ }
+
/** @see AudioManager#adjustVolume(int, int, int) */
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
-
+ if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream="+suggestedStreamType);
int streamType;
if (mVolumeControlStream != -1) {
streamType = mVolumeControlStream;
@@ -668,17 +693,27 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
// Play sounds on STREAM_RING only and if lock screen is not on.
- if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
+ if ((streamType != STREAM_REMOTE_MUSIC) &&
+ (flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
((mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING)
|| (mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()))) {
flags &= ~AudioManager.FLAG_PLAY_SOUND;
}
- adjustStreamVolume(streamType, direction, flags);
+ if (streamType == STREAM_REMOTE_MUSIC) {
+ // don't play sounds for remote
+ flags &= ~AudioManager.FLAG_PLAY_SOUND;
+ //if (DEBUG_VOL) Log.i(TAG, "Need to adjust remote volume: calling adjustRemoteVolume()");
+ adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, flags);
+ } else {
+ adjustStreamVolume(streamType, direction, flags);
+ }
}
/** @see AudioManager#adjustStreamVolume(int, int, int) */
public void adjustStreamVolume(int streamType, int direction, int flags) {
+ if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream="+streamType+", dir="+direction);
+
ensureValidDirection(direction);
ensureValidStreamType(streamType);
@@ -1370,6 +1405,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
}
int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
+ if (streamType == STREAM_REMOTE_MUSIC) {
+ // here handle remote media playback the same way as local playback
+ streamType = AudioManager.STREAM_MUSIC;
+ }
int device = getDeviceForStream(streamType);
int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device, false);
setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, false);
@@ -2172,40 +2211,61 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
// Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
return AudioSystem.STREAM_VOICE_CALL;
}
+ } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+ // Having the suggested stream be USE_DEFAULT_STREAM_TYPE is how remote control
+ // volume can have priority over STREAM_MUSIC
+ if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC");
+ return STREAM_REMOTE_MUSIC;
+ } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
+ return AudioSystem.STREAM_MUSIC;
+ } else {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default");
+ return AudioSystem.STREAM_RING;
+ }
} else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
- // Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC...");
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
return AudioSystem.STREAM_MUSIC;
- } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
- // Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING..."
- // + " b/c USE_DEFAULT_STREAM_TYPE...");
- return AudioSystem.STREAM_RING;
} else {
- // Log.v(TAG, "getActiveStreamType: Returning suggested type " + suggestedStreamType);
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
+ + suggestedStreamType);
return suggestedStreamType;
}
} else {
if (isInCommunication()) {
if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
== AudioSystem.FORCE_BT_SCO) {
- // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO");
return AudioSystem.STREAM_BLUETOOTH_SCO;
} else {
- // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL");
return AudioSystem.STREAM_VOICE_CALL;
}
} else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION,
- NOTIFICATION_VOLUME_DELAY_MS) ||
- AudioSystem.isStreamActive(AudioSystem.STREAM_RING,
+ NOTIFICATION_VOLUME_DELAY_MS) ||
+ AudioSystem.isStreamActive(AudioSystem.STREAM_RING,
NOTIFICATION_VOLUME_DELAY_MS)) {
- // Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION...");
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
return AudioSystem.STREAM_NOTIFICATION;
- } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) ||
- (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE)) {
- // Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC "
- // + " b/c USE_DEFAULT_STREAM_TYPE...");
- return AudioSystem.STREAM_MUSIC;
+ } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+ if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
+ // Having the suggested stream be USE_DEFAULT_STREAM_TYPE is how remote control
+ // volume can have priority over STREAM_MUSIC
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC");
+ return STREAM_REMOTE_MUSIC;
+ } else {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: using STREAM_MUSIC as default");
+ return AudioSystem.STREAM_MUSIC;
+ }
} else {
- // Log.v(TAG, "getActiveStreamType: Returning suggested type " + suggestedStreamType);
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
+ + suggestedStreamType);
return suggestedStreamType;
}
}
@@ -3039,6 +3099,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
mRoutesObservers.finishBroadcast();
break;
}
+
+ case MSG_REEVALUATE_REMOTE:
+ onReevaluateRemote();
+ break;
}
}
}
@@ -4106,6 +4170,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
// remote control client died, make sure the displays don't use it anymore
// by setting its remote control client to null
registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
+ // the dead client was maybe handling remote playback, reevaluate
+ postReevaluateRemote();
}
public IBinder getBinder() {
@@ -4113,7 +4179,46 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
}
+ /**
+ * A global counter for RemoteControlClient identifiers
+ */
+ private static int sLastRccId = 0;
+
+ private class RemotePlaybackState {
+ int mRccId;
+ int mVolume;
+ int mVolumeMax;
+ int mVolumeHandling;
+
+ private RemotePlaybackState(int id, int vol, int volMax) {
+ mRccId = id;
+ mVolume = vol;
+ mVolumeMax = volMax;
+ mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
+ }
+ }
+
+ /**
+ * Internal cache for the playback information of the RemoteControlClient whose volume gets to
+ * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack
+ * every time we need this info.
+ */
+ private RemotePlaybackState mMainRemote;
+ /**
+ * Indicates whether the "main" RemoteControlClient is considered active.
+ * Use synchronized on mMainRemote.
+ */
+ private boolean mMainRemoteIsActive;
+ /**
+ * Indicates whether there is remote playback going on. True even if there is no "active"
+ * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it
+ * handles remote playback.
+ * Use synchronized on mMainRemote.
+ */
+ private boolean mHasRemotePlayback;
+
private static class RemoteControlStackEntry {
+ public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
/**
* The target for the ACTION_MEDIA_BUTTON events.
* Always non null.
@@ -4132,6 +4237,24 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
* but no remote control client has been registered) */
public IRemoteControlClient mRcClient;
public RcClientDeathHandler mRcClientDeathHandler;
+ /**
+ * Information only used for non-local playback
+ */
+ public int mPlaybackType;
+ public int mPlaybackVolume;
+ public int mPlaybackVolumeMax;
+ public int mPlaybackVolumeHandling;
+ public int mPlaybackStream;
+ public int mPlaybackState;
+
+ public void resetPlaybackInfo() {
+ mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
+ mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
+ mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
+ mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
+ mPlaybackStream = AudioManager.STREAM_MUSIC;
+ mPlaybackState = RemoteControlClient.PLAYSTATE_STOPPED;
+ }
/** precondition: mediaIntent != null, eventReceiver != null */
public RemoteControlStackEntry(PendingIntent mediaIntent, ComponentName eventReceiver) {
@@ -4139,6 +4262,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
mReceiverComponent = eventReceiver;
mCallingUid = -1;
mRcClient = null;
+ mRccId = ++sLastRccId;
+
+ resetPlaybackInfo();
}
public void unlinkToRcClientDeath() {
@@ -4188,9 +4314,44 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
pw.println(" pi: " + rcse.mMediaIntent +
" -- ercvr: " + rcse.mReceiverComponent +
" -- client: " + rcse.mRcClient +
- " -- uid: " + rcse.mCallingUid);
+ " -- uid: " + rcse.mCallingUid +
+ " -- type: " + rcse.mPlaybackType +
+ " state: " + rcse.mPlaybackState);
+ }
+ }
+ }
+
+ /**
+ * Helper function:
+ * Display in the log the current entries in the remote control stack, focusing
+ * on RemoteControlClient data
+ */
+ private void dumpRCCStack(PrintWriter pw) {
+ pw.println("\nRemote Control Client stack entries:");
+ synchronized(mRCStack) {
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ pw.println(" uid: " + rcse.mCallingUid +
+ " -- id: " + rcse.mRccId +
+ " -- type: " + rcse.mPlaybackType +
+ " -- state: " + rcse.mPlaybackState +
+ " -- vol handling: " + rcse.mPlaybackVolumeHandling +
+ " -- vol: " + rcse.mPlaybackVolume +
+ " -- volMax: " + rcse.mPlaybackVolumeMax);
}
}
+ synchronized (mMainRemote) {
+ pw.println("\nRemote Volume State:");
+ pw.println(" has remote: " + mHasRemotePlayback);
+ pw.println(" is remote active: " + mMainRemoteIsActive);
+ pw.println(" rccId: " + mMainRemote.mRccId);
+ pw.println(" volume handling: "
+ + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ?
+ "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)"));
+ pw.println(" volume: " + mMainRemote.mVolume);
+ pw.println(" volume steps: " + mMainRemote.mVolumeMax);
+ }
}
/**
@@ -4559,13 +4720,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
/**
* see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
+ * @return the unique ID of the RemoteControlStackEntry associated with the RemoteControlClient
* Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient
* without modifying the RC stack, but while still causing the display to refresh (will
* become blank as a result of this)
*/
- public void registerRemoteControlClient(PendingIntent mediaIntent,
+ public int registerRemoteControlClient(PendingIntent mediaIntent,
IRemoteControlClient rcClient, String callingPackageName) {
if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
+ int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
// store the new display information
@@ -4584,8 +4747,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
rcse.mCallingUid = Binder.getCallingUid();
if (rcClient == null) {
// here rcse.mRcClientDeathHandler is null;
+ rcse.resetPlaybackInfo();
break;
}
+ rccId = rcse.mRccId;
// there is a new (non-null) client:
// 1/ give the new client the current display (if any)
@@ -4619,6 +4784,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
}
}
+ return rccId;
}
/**
@@ -4793,6 +4959,248 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
}
+ // FIXME send a message instead of updating the stack synchronously
+ public void setPlaybackInfoForRcc(int rccId, int what, int value) {
+ if(DEBUG_RC) Log.d(TAG, "setPlaybackInfoForRcc(id="+rccId+", what="+what+",val="+value+")");
+ synchronized(mRCStack) {
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ if (rcse.mRccId == rccId) {
+ switch (what) {
+ case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE:
+ rcse.mPlaybackType = value;
+ postReevaluateRemote();
+ break;
+ case RemoteControlClient.PLAYBACKINFO_VOLUME:
+ rcse.mPlaybackVolume = value;
+ synchronized (mMainRemote) {
+ if (rccId == mMainRemote.mRccId) {
+ mMainRemote.mVolume = value;
+ mVolumePanel.postHasNewRemotePlaybackInfo();
+ }
+ }
+ break;
+ case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX:
+ rcse.mPlaybackVolumeMax = value;
+ synchronized (mMainRemote) {
+ if (rccId == mMainRemote.mRccId) {
+ mMainRemote.mVolumeMax = value;
+ mVolumePanel.postHasNewRemotePlaybackInfo();
+ }
+ }
+ break;
+ case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING:
+ rcse.mPlaybackVolumeHandling = value;
+ synchronized (mMainRemote) {
+ if (rccId == mMainRemote.mRccId) {
+ mMainRemote.mVolumeHandling = value;
+ mVolumePanel.postHasNewRemotePlaybackInfo();
+ }
+ }
+ break;
+ case RemoteControlClient.PLAYBACKINFO_USES_STREAM:
+ rcse.mPlaybackStream = value;
+ break;
+ case RemoteControlClient.PLAYBACKINFO_PLAYSTATE:
+ rcse.mPlaybackState = value;
+ synchronized (mMainRemote) {
+ if (rccId == mMainRemote.mRccId) {
+ mMainRemoteIsActive = isPlaystateActive(value);
+ postReevaluateRemote();
+ }
+ }
+ break;
+ default:
+ Log.e(TAG, "unhandled key " + what + " for RCC " + rccId);
+ break;
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if a remote client is active on the supplied stream type. Update the remote stream
+ * volume state if found and playing
+ * @param streamType
+ * @return false if no remote playing is currently playing
+ */
+ private boolean checkUpdateRemoteStateIfActive(int streamType) {
+ synchronized(mRCStack) {
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
+ && isPlaystateActive(rcse.mPlaybackState)
+ && (rcse.mPlaybackStream == streamType)) {
+ if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
+ + ", vol =" + rcse.mPlaybackVolume);
+ synchronized (mMainRemote) {
+ mMainRemote.mRccId = rcse.mRccId;
+ mMainRemote.mVolume = rcse.mPlaybackVolume;
+ mMainRemote.mVolumeMax = rcse.mPlaybackVolumeMax;
+ mMainRemote.mVolumeHandling = rcse.mPlaybackVolumeHandling;
+ mMainRemoteIsActive = true;
+ }
+ return true;
+ }
+ }
+ }
+ synchronized (mMainRemote) {
+ mMainRemoteIsActive = false;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the given playback state is considered "active", i.e. it describes a state
+ * where playback is happening, or about to
+ * @param playState the playback state to evaluate
+ * @return true if active, false otherwise (inactive or unknown)
+ */
+ private static boolean isPlaystateActive(int playState) {
+ switch (playState) {
+ case RemoteControlClient.PLAYSTATE_PLAYING:
+ case RemoteControlClient.PLAYSTATE_BUFFERING:
+ case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
+ case RemoteControlClient.PLAYSTATE_REWINDING:
+ case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
+ case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void adjustRemoteVolume(int streamType, int direction, int flags) {
+ int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
+ boolean volFixed = false;
+ synchronized (mMainRemote) {
+ if (!mMainRemoteIsActive) {
+ if (DEBUG_VOL) Log.w(TAG, "adjustRemoteVolume didn't find an active client");
+ return;
+ }
+ rccId = mMainRemote.mRccId;
+ volFixed = (mMainRemote.mVolumeHandling ==
+ RemoteControlClient.PLAYBACK_VOLUME_FIXED);
+ }
+ // unlike "local" stream volumes, we can't compute the new volume based on the direction,
+ // we can only notify the remote that volume needs to be updated, and we'll get an async'
+ // update through setPlaybackInfoForRcc()
+ if (!volFixed) {
+ sendVolumeUpdateToRemote(rccId, direction);
+ }
+
+ // fire up the UI
+ mVolumePanel.postRemoteVolumeChanged(streamType, flags);
+ }
+
+ private void sendVolumeUpdateToRemote(int rccId, int direction) {
+ if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
+ if (direction == 0) {
+ // only handling discrete events
+ return;
+ }
+ String packageForRcc = null;
+ synchronized (mRCStack) {
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
+ if (rcse.mRccId == rccId) {
+ packageForRcc = rcse.mReceiverComponent.getPackageName();
+ break;
+ }
+ }
+ }
+ if (packageForRcc != null) {
+ Intent intent = new Intent(Intent.ACTION_VOLUME_UPDATE);
+ intent.putExtra(Intent.EXTRA_VOLUME_UPDATE_DIRECTION, direction);
+ intent.setPackage(packageForRcc);
+ mContext.sendBroadcast(intent);
+ }
+ }
+
+ public int getRemoteStreamMaxVolume() {
+ synchronized (mMainRemote) {
+ if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
+ return 0;
+ }
+ return mMainRemote.mVolumeMax;
+ }
+ }
+
+ public int getRemoteStreamVolume() {
+ synchronized (mMainRemote) {
+ if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
+ return 0;
+ }
+ return mMainRemote.mVolume;
+ }
+ }
+
+ public void setRemoteStreamVolume(int vol) {
+ if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); }
+ int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
+ synchronized (mMainRemote) {
+ if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
+ return;
+ }
+ rccId = mMainRemote.mRccId;
+ }
+ String packageForRcc = null;
+ synchronized (mRCStack) {
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ if (rcse.mRccId == rccId) {
+ //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
+ packageForRcc = rcse.mReceiverComponent.getPackageName();
+ break;
+ }
+ }
+ }
+ if (packageForRcc != null) {
+ Intent intent = new Intent(Intent.ACTION_VOLUME_UPDATE);
+ intent.putExtra(Intent.EXTRA_VOLUME_UPDATE_VALUE, vol);
+ intent.setPackage(packageForRcc);
+ mContext.sendBroadcast(intent);
+ }
+ }
+
+ /**
+ * Call to make AudioService reevaluate whether it's in a mode where remote players should
+ * have their volume controlled. In this implementation this is only to reset whether
+ * VolumePanel should display remote volumes
+ */
+ private void postReevaluateRemote() {
+ sendMsg(mAudioHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
+ }
+
+ private void onReevaluateRemote() {
+ if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); }
+ // is there a registered RemoteControlClient that is handling remote playback
+ boolean hasRemotePlayback = false;
+ synchronized (mRCStack) {
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ if (rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
+ hasRemotePlayback = true;
+ break;
+ }
+ }
+ }
+ synchronized (mMainRemote) {
+ if (mHasRemotePlayback != hasRemotePlayback) {
+ mHasRemotePlayback = hasRemotePlayback;
+ mVolumePanel.postRemoteSliderVisibility(hasRemotePlayback);
+ }
+ }
+ }
+
//==========================================================================================
// Device orientation
//==========================================================================================
@@ -4874,9 +5282,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
- // TODO probably a lot more to do here than just the audio focus and remote control stacks
dumpFocusStack(pw);
dumpRCStack(pw);
+ dumpRCCStack(pw);
dumpStreamStates(pw);
pw.println("\nAudio routes:");
pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mMainType));
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index fc5b8f1..83483c6 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -35,6 +35,8 @@ interface IAudioService {
void adjustVolume(int direction, int flags);
+ oneway void adjustLocalOrRemoteStreamVolume(int streamType, int direction);
+
void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
void adjustStreamVolume(int streamType, int direction, int flags);
@@ -43,6 +45,8 @@ interface IAudioService {
void setStreamVolume(int streamType, int index, int flags);
+ oneway void setRemoteStreamVolume(int index);
+
void setMasterVolume(int index, int flags);
void setStreamSolo(int streamType, boolean state, IBinder cb);
@@ -119,7 +123,7 @@ interface IAudioService {
oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c);
oneway void unregisterMediaButtonEventReceiverForCalls();
- oneway void registerRemoteControlClient(in PendingIntent mediaIntent,
+ int registerRemoteControlClient(in PendingIntent mediaIntent,
in IRemoteControlClient rcClient, in String callingPackageName);
oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
in IRemoteControlClient rcClient);
@@ -128,6 +132,10 @@ interface IAudioService {
oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd);
oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h);
+ oneway void setPlaybackInfoForRcc(int rccId, int what, int value);
+ int getRemoteStreamMaxVolume();
+ int getRemoteStreamVolume();
+
void startBluetoothSco(IBinder cb);
void stopBluetoothSco(IBinder cb);
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index f1c4d34..5b8035e 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -18,6 +18,7 @@ package android.media;
import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -26,9 +27,11 @@ import android.graphics.RectF;
import android.media.MediaMetadataRetriever;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
@@ -131,6 +134,88 @@ public class RemoteControlClient
public final static int PLAYSTATE_NONE = 0;
/**
+ * @hide (to be un-hidden)
+ * The default playback type, "local", indicating the presentation of the media is happening on
+ * the same device (e.g. a phone, a tablet) as where it is controlled from.
+ */
+ public final static int PLAYBACK_TYPE_LOCAL = 0;
+ /**
+ * @hide (to be un-hidden)
+ * A playback type indicating the presentation of the media is happening on
+ * a different device (i.e. the remote device) than where it is controlled from.
+ */
+ public final static int PLAYBACK_TYPE_REMOTE = 1;
+ private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL;
+ private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE;
+ /**
+ * @hide (to be un-hidden)
+ * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled
+ * from this object. An example of fixed playback volume is a remote player, playing over HDMI
+ * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the
+ * source.
+ * @see #PLAYBACKINFO_VOLUME_HANDLING.
+ */
+ public final static int PLAYBACK_VOLUME_FIXED = 0;
+ /**
+ * @hide (to be un-hidden)
+ * Playback information indicating the playback volume is variable and can be controlled from
+ * this object.
+ * @see #PLAYBACKINFO_VOLUME_HANDLING.
+ */
+ public final static int PLAYBACK_VOLUME_VARIABLE = 1;
+ /**
+ * @hide (to be un-hidden)
+ * The playback information value indicating the value of a given information type is invalid.
+ * @see #PLAYBACKINFO_VOLUME_HANDLING.
+ */
+ public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;
+
+ //==========================================
+ // Public keys for playback information
+ /**
+ * @hide (to be un-hidden)
+ * Playback information that defines the type of playback associated with this
+ * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}.
+ */
+ public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1;
+ /**
+ * @hide (to be un-hidden)
+ * Playback information that defines at what volume the playback associated with this
+ * RemoteControlClient is performed. This information is only used when the playback type is not
+ * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
+ */
+ public final static int PLAYBACKINFO_VOLUME = 2;
+ /**
+ * @hide (to be un-hidden)
+ * Playback information that defines the maximum volume volume value that is supported
+ * by the playback associated with this RemoteControlClient. This information is only used
+ * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
+ */
+ public final static int PLAYBACKINFO_VOLUME_MAX = 3;
+ /**
+ * @hide (to be un-hidden)
+ * Playback information that defines how volume is handled for the presentation of the media.
+ * @see #PLAYBACK_VOLUME_FIXED
+ * @see #PLAYBACK_VOLUME_VARIABLE
+ */
+ public final static int PLAYBACKINFO_VOLUME_HANDLING = 4;
+ /**
+ * @hide (to be un-hidden)
+ * Playback information that defines over what stream type the media is presented.
+ */
+ public final static int PLAYBACKINFO_USES_STREAM = 5;
+
+ //==========================================
+ // Private keys for playback information
+ /**
+ * @hide
+ * Used internally to relay playback state (set by the application with
+ * {@link #setPlaybackState(int)}) to AudioService
+ */
+ public final static int PLAYBACKINFO_PLAYSTATE = 255;
+
+
+ /**
* Flag indicating a RemoteControlClient makes use of the "previous" media key.
*
* @see #setTransportControlFlags(int)
@@ -516,6 +601,8 @@ public class RemoteControlClient
// send to remote control display if conditions are met
sendPlaybackState_syncCacheLock();
+ // update AudioService
+ sendAudioServiceNewPlaybackInfo_syncCacheLock(PLAYBACKINFO_PLAYSTATE, state);
}
}
}
@@ -542,6 +629,122 @@ public class RemoteControlClient
}
}
+ /** @hide */
+ public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
+ /** @hide */
+ // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
+ public final static int DEFAULT_PLAYBACK_VOLUME = 15;
+
+ private int mPlaybackType = PLAYBACK_TYPE_LOCAL;
+ private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME;
+ private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME;
+ private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING;
+ private int mPlaybackStream = AudioManager.STREAM_MUSIC;
+
+ /**
+ * @hide (to be un-hidden)
+ * Set information describing information related to the playback of media so the system
+ * can implement additional behavior to handle non-local playback usecases.
+ * @param what a key to specify the type of information to set. Valid keys are
+ * {@link #PLAYBACKINFO_PLAYBACK_TYPE},
+ * {@link #PLAYBACKINFO_USES_STREAM},
+ * {@link #PLAYBACKINFO_VOLUME},
+ * {@link #PLAYBACKINFO_VOLUME_MAX},
+ * and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
+ * @param value the value for the supplied information to set.
+ */
+ public void setPlaybackInformation(int what, int value) {
+ synchronized(mCacheLock) {
+ switch (what) {
+ case PLAYBACKINFO_PLAYBACK_TYPE:
+ if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) {
+ if (mPlaybackType != value) {
+ mPlaybackType = value;
+ sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
+ }
+ } else {
+ Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE");
+ }
+ break;
+ case PLAYBACKINFO_VOLUME:
+ if ((value > -1) && (value <= mPlaybackVolumeMax)) {
+ if (mPlaybackVolume != value) {
+ mPlaybackVolume = value;
+ sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
+ }
+ } else {
+ Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME");
+ }
+ break;
+ case PLAYBACKINFO_VOLUME_MAX:
+ if (value > 0) {
+ if (mPlaybackVolumeMax != value) {
+ mPlaybackVolumeMax = value;
+ sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
+ }
+ } else {
+ Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX");
+ }
+ break;
+ case PLAYBACKINFO_USES_STREAM:
+ if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) {
+ mPlaybackStream = value;
+ } else {
+ Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM");
+ }
+ break;
+ case PLAYBACKINFO_VOLUME_HANDLING:
+ if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) {
+ if (mPlaybackVolumeHandling != value) {
+ mPlaybackVolumeHandling = value;
+ sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
+ }
+ } else {
+ Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING");
+ }
+ break;
+ default:
+ // not throwing an exception or returning an error if more keys are to be
+ // supported in the future
+ Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what);
+ break;
+ }
+ }
+ }
+
+ /**
+ * @hide (to be un-hidden)
+ * Return playback information represented as an integer value.
+ * @param what a key to specify the type of information to retrieve. Valid keys are
+ * {@link #PLAYBACKINFO_PLAYBACK_TYPE},
+ * {@link #PLAYBACKINFO_USES_STREAM},
+ * {@link #PLAYBACKINFO_VOLUME},
+ * {@link #PLAYBACKINFO_VOLUME_MAX},
+ * and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
+ * @return the current value for the given information type, or
+ * {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or
+ * the value is unknown.
+ */
+ public int getIntPlaybackInformation(int what) {
+ synchronized(mCacheLock) {
+ switch (what) {
+ case PLAYBACKINFO_PLAYBACK_TYPE:
+ return mPlaybackType;
+ case PLAYBACKINFO_VOLUME:
+ return mPlaybackVolume;
+ case PLAYBACKINFO_VOLUME_MAX:
+ return mPlaybackVolumeMax;
+ case PLAYBACKINFO_USES_STREAM:
+ return mPlaybackStream;
+ case PLAYBACKINFO_VOLUME_HANDLING:
+ return mPlaybackVolumeHandling;
+ default:
+ Log.e(TAG, "getIntPlaybackInformation() unknown key " + what);
+ return PLAYBACKINFO_INVALID_VALUE;
+ }
+ }
+ }
+
/**
* Lock for all cached data
*/
@@ -675,6 +878,27 @@ public class RemoteControlClient
}
};
+ /**
+ * @hide
+ * Default value for the unique identifier
+ */
+ public final static int RCSE_ID_UNREGISTERED = -1;
+ /**
+ * Unique identifier of the RemoteControlStackEntry in AudioService with which
+ * this RemoteControlClient is associated.
+ */
+ private int mRcseId = RCSE_ID_UNREGISTERED;
+ /**
+ * @hide
+ * To be only used by AudioManager after it has received the unique id from
+ * IAudioService.registerRemoteControlClient()
+ * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which
+ * this RemoteControlClient is associated.
+ */
+ public void setRcseId(int id) {
+ mRcseId = id;
+ }
+
private EventHandler mEventHandler;
private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
private final static int MSG_REQUEST_METADATA = 2;
@@ -731,6 +955,9 @@ public class RemoteControlClient
}
}
+ //===========================================================
+ // Communication with IRemoteControlDisplay
+
private void detachFromDisplay_syncCacheLock() {
mRcDisplay = null;
mArtworkExpectedWidth = ARTWORK_INVALID_SIZE;
@@ -802,6 +1029,37 @@ public class RemoteControlClient
}
}
+ //===========================================================
+ // Communication with AudioService
+
+ private static IAudioService sService;
+
+ private static IAudioService getService()
+ {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ sService = IAudioService.Stub.asInterface(b);
+ return sService;
+ }
+
+ private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) {
+ if (mRcseId == RCSE_ID_UNREGISTERED) {
+ return;
+ }
+ Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value);
+ IAudioService service = getService();
+ try {
+ service.setPlaybackInfoForRcc(mRcseId, what, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in sendAudioServiceNewPlaybackInfo_syncCacheLock", e);
+ }
+ }
+
+ //===========================================================
+ // Message handlers
+
private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) {
synchronized (mCacheLock) {
// this remote control client is told it is the "focused" one:
@@ -836,6 +1094,9 @@ public class RemoteControlClient
}
}
+ //===========================================================
+ // Internal utilities
+
/**
* 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.