Minimum work to make volume handling work with sessions

This is the minimum change to make adjusting volume work with
MediaSessions. This only affects adjusting the volume and adjusting
the volume with a suggested stream. Adjusting a specific stream or
setting a specific stream will still use the same code.

This does not fix existing remote volume handling in RCC, which
will require a separate change to MediaController.

Change-Id: I5b957ff4bece1ee11e2364e1f216e1c08343c983
This commit is contained in:
RoboErik
2014-05-30 14:57:59 -07:00
parent 283c907a6a
commit b69ffd4dc2
11 changed files with 326 additions and 13 deletions

View File

@@ -635,7 +635,12 @@ public class AudioManager {
if (mUseMasterVolume) {
service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
} else {
service.adjustVolume(direction, flags, mContext.getOpPackageName());
if (USE_SESSIONS) {
MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
} else {
service.adjustVolume(direction, flags, mContext.getOpPackageName());
}
}
} catch (RemoteException e) {
Log.e(TAG, "Dead object in adjustVolume", e);
@@ -665,8 +670,13 @@ public class AudioManager {
if (mUseMasterVolume) {
service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
} else {
service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags,
mContext.getOpPackageName());
if (USE_SESSIONS) {
MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
} else {
service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags,
mContext.getOpPackageName());
}
}
} catch (RemoteException e) {
Log.e(TAG, "Dead object in adjustSuggestedStreamVolume", e);

View File

@@ -47,4 +47,8 @@ interface ISession {
void setMetadata(in MediaMetadata metadata);
void setPlaybackState(in PlaybackState state);
void setRatingType(int type);
// These commands relate to volume handling
void configureVolumeHandling(int type, int arg1, int arg2);
void setCurrentVolume(int currentVolume);
}

View File

@@ -45,4 +45,8 @@ oneway interface ISessionCallback {
void onRewind();
void onSeekTo(long pos);
void onRate(in Rating rating);
// These callbacks are for volume handling
void onAdjustVolumeBy(int delta);
void onSetVolumeTo(int value);
}

View File

@@ -29,4 +29,5 @@ interface ISessionManager {
ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
List<IBinder> getSessions(in ComponentName compName, int userId);
void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock);
void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags);
}

View File

@@ -124,9 +124,21 @@ public final class MediaSession {
*/
public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5;
private static final String KEY_COMMAND = "command";
private static final String KEY_EXTRAS = "extras";
private static final String KEY_CALLBACK = "callback";
/**
* The session uses local playback. Used for configuring volume handling
* with the system.
*
* @hide
*/
public static final int VOLUME_TYPE_LOCAL = 1;
/**
* The session uses remote playback. Used for configuring volume handling
* with the system.
*
* @hide
*/
public static final int VOLUME_TYPE_REMOTE = 2;
private final Object mLock = new Object();
@@ -143,6 +155,7 @@ public final class MediaSession {
= new ArrayMap<String, RouteInterface.EventListener>();
private Route mRoute;
private RemoteVolumeProvider mVolumeProvider;
private boolean mActive = false;;
@@ -242,7 +255,11 @@ public final class MediaSession {
* @param stream The {@link AudioManager} stream this session is playing on.
*/
public void setPlaybackToLocal(int stream) {
// TODO
try {
mBinder.configureVolumeHandling(VOLUME_TYPE_LOCAL, stream, 0);
} catch (RemoteException e) {
Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
}
}
/**
@@ -259,7 +276,14 @@ public final class MediaSession {
if (volumeProvider == null) {
throw new IllegalArgumentException("volumeProvider may not be null!");
}
// TODO
mVolumeProvider = volumeProvider;
try {
mBinder.configureVolumeHandling(VOLUME_TYPE_REMOTE, volumeProvider.getVolumeControl(),
volumeProvider.getMaxVolume());
} catch (RemoteException e) {
Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
}
}
/**
@@ -942,6 +966,26 @@ public final class MediaSession {
}
/*
* (non-Javadoc)
* @see android.media.session.ISessionCallback#onAdjustVolumeBy(int)
*/
@Override
public void onAdjustVolumeBy(int delta) throws RemoteException {
// TODO(epastern): Auto-generated method stub
}
/*
* (non-Javadoc)
* @see android.media.session.ISessionCallback#onSetVolumeTo(int)
*/
@Override
public void onSetVolumeTo(int value) throws RemoteException {
// TODO(epastern): Auto-generated method stub
}
}
private class CallbackMessageHandler extends Handler {

View File

@@ -76,6 +76,13 @@ public class MediaSessionLegacyHelper {
}
}
public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
mSessionManager.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
if (DEBUG) {
Log.d(TAG, "dispatched volume adjustment");
}
}
public void addRccListener(PendingIntent pi,
MediaSession.TransportControlsCallback listener) {
if (pi == null) {

View File

@@ -166,4 +166,22 @@ public final class MediaSessionManager {
Log.e(TAG, "Failed to send key event.", e);
}
}
/**
* Dispatch an adjust volume request to the system. It will be routed to the
* most relevant stream/session.
*
* @param suggestedStream The stream to fall back to if there isn't a
* relevant stream
* @param delta The amount to adjust the volume by.
* @param flags Any flags to include with the volume change.
* @hide
*/
public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags) {
try {
mService.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send adjust volume.", e);
}
}
}

View File

@@ -25,6 +25,7 @@ import android.media.session.ISessionControllerCallback;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.media.session.MediaController;
import android.media.session.RemoteVolumeProvider;
import android.media.session.RouteCommand;
import android.media.session.RouteInfo;
import android.media.session.RouteOptions;
@@ -33,6 +34,7 @@ import android.media.session.MediaSession;
import android.media.session.MediaSessionInfo;
import android.media.session.RouteInterface;
import android.media.session.PlaybackState;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
import android.os.Bundle;
@@ -112,6 +114,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
private long mLastActiveTime;
// End TransportPerformer fields
// Volume handling fields
private int mPlaybackType = MediaSession.VOLUME_TYPE_LOCAL;
private int mAudioStream = AudioManager.STREAM_MUSIC;
private int mVolumeControlType = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE;
private int mMaxVolume = 0;
private int mCurrentVolume = 0;
// End volume handling fields
private boolean mIsActive = false;
private boolean mDestroyed = false;
@@ -247,6 +257,27 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
mSessionCb.sendRouteEvent(event);
}
/**
* Send a volume adjustment to the session owner.
*
* @param delta The amount to adjust the volume by.
*/
public void adjustVolumeBy(int delta) {
if (mVolumeControlType == RemoteVolumeProvider.VOLUME_CONTROL_FIXED) {
// Nothing to do, the volume cannot be changed
return;
}
mSessionCb.adjustVolumeBy(delta);
}
public void setVolumeTo(int value) {
if (mVolumeControlType != RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
// Nothing to do. The volume can't be set directly.
return;
}
mSessionCb.setVolumeTo(value);
}
/**
* Set the connection to use for the selected route and notify the app it is
* now connected.
@@ -294,14 +325,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
* Check if the session is currently performing playback. This will also
* return true if the session was recently paused.
*
* @param includeRecentlyActive True if playback that was recently paused
* should count, false if it shouldn't.
* @return True if the session is performing playback, false otherwise.
*/
public boolean isPlaybackActive() {
public boolean isPlaybackActive(boolean includeRecentlyActive) {
int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
if (isActiveState(state)) {
return true;
}
if (state == mPlaybackState.STATE_PAUSED) {
if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
if (inactiveTime < ACTIVE_BUFFER) {
return true;
@@ -310,6 +343,54 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
return false;
}
/**
* Get the type of playback, either local or remote.
*
* @return The current type of playback.
*/
public int getPlaybackType() {
return mPlaybackType;
}
/**
* Get the local audio stream being used. Only valid if playback type is
* local.
*
* @return The audio stream the session is using.
*/
public int getAudioStream() {
return mAudioStream;
}
/**
* Get the type of volume control. Only valid if playback type is remote.
*
* @return The volume control type being used.
*/
public int getVolumeControl() {
return mVolumeControlType;
}
/**
* Get the max volume that can be set. Only valid if playback type is
* remote.
*
* @return The max volume that can be set.
*/
public int getMaxVolume() {
return mMaxVolume;
}
/**
* Get the current volume for this session. Only valid if playback type is
* remote.
*
* @return The current volume of the remote playback.
*/
public int getCurrentVolume() {
return mCurrentVolume;
}
/**
* @return True if this session is currently connected to a route.
*/
@@ -640,6 +721,40 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
mRequests.add(request);
}
}
@Override
public void setCurrentVolume(int volume) {
mCurrentVolume = volume;
}
@Override
public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException {
switch(type) {
case MediaSession.VOLUME_TYPE_LOCAL:
mPlaybackType = type;
int audioStream = arg1;
if (isValidStream(audioStream)) {
mAudioStream = audioStream;
} else {
Log.e(TAG, "Cannot set stream to " + audioStream + ". Using music stream");
mAudioStream = AudioManager.STREAM_MUSIC;
}
break;
case MediaSession.VOLUME_TYPE_REMOTE:
mPlaybackType = type;
mVolumeControlType = arg1;
mMaxVolume = arg2;
break;
default:
throw new IllegalArgumentException("Volume handling type " + type
+ " not recognized.");
}
}
private boolean isValidStream(int stream) {
return stream >= AudioManager.STREAM_VOICE_CALL
&& stream <= AudioManager.STREAM_NOTIFICATION;
}
}
class SessionCb {
@@ -780,6 +895,22 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
Slog.e(TAG, "Remote failure in rate.", e);
}
}
public void adjustVolumeBy(int delta) {
try {
mCb.onAdjustVolumeBy(delta);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
}
}
public void setVolumeTo(int value) {
try {
mCb.onSetVolumeTo(value);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
}
}
}
class ControllerStub extends ISessionController.Stub {

View File

@@ -26,6 +26,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.IAudioService;
import android.media.routeprovider.RouteRequest;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
@@ -40,6 +42,7 @@ import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.speech.RecognizerIntent;
@@ -79,6 +82,7 @@ public class MediaSessionService extends SystemService implements Monitor {
private final PowerManager.WakeLock mMediaEventWakeLock;
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
private MediaSessionRecord mPrioritySession;
private int mCurrentUserId = -1;
@@ -105,6 +109,12 @@ public class MediaSessionService extends SystemService implements Monitor {
updateUser();
mKeyguardManager =
(KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
mAudioService = getAudioService();
}
private IAudioService getAudioService() {
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
return IAudioService.Stub.asInterface(b);
}
/**
@@ -702,6 +712,23 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
@Override
public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
throws RemoteException {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
MediaSessionRecord session = mPriorityStack
.getDefaultVolumeSession(mCurrentUserId);
dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
@@ -737,6 +764,49 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
MediaSessionRecord session) {
int direction = 0;
int steps = delta;
if (delta > 0) {
direction = 1;
} else if (delta < 0) {
direction = -1;
steps = -delta;
}
if (DEBUG) {
String sessionInfo = session == null ? null : session.getSessionInfo().toString();
Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
+ ", suggestedStream=" + suggestedStream);
}
if (session == null) {
for (int i = 0; i < steps; i++) {
try {
mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
flags, getContext().getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error adjusting default volume.", e);
}
}
} else {
if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) {
for (int i = 0; i < steps; i++) {
try {
mAudioService.adjustSuggestedStreamVolume(direction,
session.getAudioStream(), flags,
getContext().getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error adjusting volume for stream "
+ session.getAudioStream(), e);
}
}
} else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) {
session.adjustVolumeBy(delta);
}
}
}
private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
MediaSessionRecord session) {
if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {

View File

@@ -52,6 +52,7 @@ public class MediaSessionStack {
private MediaSessionRecord mCachedButtonReceiver;
private MediaSessionRecord mCachedDefault;
private MediaSessionRecord mCachedVolumeDefault;
private ArrayList<MediaSessionRecord> mCachedActiveList;
private ArrayList<MediaSessionRecord> mCachedTransportControlList;
@@ -93,6 +94,9 @@ public class MediaSessionStack {
mSessions.remove(record);
mSessions.add(0, record);
clearCache();
} else if (newState == PlaybackState.STATE_PAUSED) {
// Just clear the volume cache in this case
mCachedVolumeDefault = null;
}
}
@@ -177,6 +181,25 @@ public class MediaSessionStack {
return mCachedButtonReceiver;
}
public MediaSessionRecord getDefaultVolumeSession(int userId) {
if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
return mGlobalPrioritySession;
}
if (mCachedVolumeDefault != null) {
return mCachedVolumeDefault;
}
ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
int size = records.size();
for (int i = 0; i < size; i++) {
MediaSessionRecord record = records.get(i);
if (record.isPlaybackActive(false)) {
mCachedVolumeDefault = record;
return record;
}
}
return null;
}
public void dump(PrintWriter pw, String prefix) {
ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
UserHandle.USER_ALL);
@@ -237,7 +260,7 @@ public class MediaSessionStack {
lastLocalIndex++;
lastActiveIndex++;
lastPublishedIndex++;
} else if (session.isPlaybackActive()) {
} else if (session.isPlaybackActive(true)) {
// TODO replace getRoute() == null with real local route check
if(session.getRoute() == null) {
// Active local sessions get top priority
@@ -284,6 +307,7 @@ public class MediaSessionStack {
private void clearCache() {
mCachedDefault = null;
mCachedVolumeDefault = null;
mCachedButtonReceiver = null;
mCachedActiveList = null;
mCachedTransportControlList = null;

View File

@@ -82,7 +82,7 @@ public class PlayerSession {
Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
mSession = man.createSession("OneMedia");
mSession.addCallback(mCallback);
mSession.addTransportControlsCallback(new TransportListener());
mSession.addTransportControlsCallback(new TransportCallback());
mSession.setPlaybackState(mPlaybackState);
mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setRouteOptions(mRouteOptions);
@@ -255,7 +255,7 @@ public class PlayerSession {
}
}
private class TransportListener extends MediaSession.TransportControlsCallback {
private class TransportCallback extends MediaSession.TransportControlsCallback {
@Override
public void onPlay() {
mRenderer.onPlay();