Merge "Remote volume handling" into jb-dev

This commit is contained in:
Jean-Michel Trivi
2012-06-19 10:54:32 -07:00
committed by Android (Google) Code Review
8 changed files with 940 additions and 54 deletions

View File

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

View File

@ -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);
@ -2169,40 +2208,61 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
// Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
return AudioSystem.STREAM_VOICE_CALL;
}
} else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
// Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC...");
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;
// 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)) {
if (DEBUG_VOL)
Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
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;
}
} 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;
}
}
@ -3036,6 +3096,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
mRoutesObservers.finishBroadcast();
break;
}
case MSG_REEVALUATE_REMOTE:
onReevaluateRemote();
break;
}
}
}
@ -4103,6 +4167,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() {
@ -4110,7 +4176,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.
@ -4129,6 +4234,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) {
@ -4136,6 +4259,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
mReceiverComponent = eventReceiver;
mCallingUid = -1;
mRcClient = null;
mRccId = ++sLastRccId;
resetPlaybackInfo();
}
public void unlinkToRcClientDeath() {
@ -4185,11 +4311,46 @@ 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);
}
}
/**
* Helper function:
* Remove any entry in the remote control stack that has the same package name as packageName
@ -4556,13 +4717,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
@ -4581,8 +4744,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)
@ -4616,6 +4781,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
}
}
return rccId;
}
/**
@ -4790,6 +4956,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
//==========================================================================================
@ -4871,9 +5279,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));

View File

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

View File

@ -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;
@ -130,6 +133,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.
*
@ -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.