diff options
author | RoboErik <epastern@google.com> | 2014-05-30 14:57:59 -0700 |
---|---|---|
committer | RoboErik <epastern@google.com> | 2014-05-30 15:47:58 -0700 |
commit | b69ffd4dc2c8fa85e0064151141ebeee90de471e (patch) | |
tree | da787f506e0ac8a23a268f199096cb27e87c4f8e | |
parent | 283c907a6a84c5d9ffe38d3468e76131e6917105 (diff) |
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
11 files changed, 326 insertions, 13 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index f4affa03899f..215615fae880 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -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); diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index c4233c305192..1cfc5bc3f14a 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -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); }
\ No newline at end of file diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl index 103c3f160e4e..0316d1fa5544 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/java/android/media/session/ISessionCallback.aidl @@ -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); }
\ No newline at end of file diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index 38b929327f46..6d9888f09c14 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -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); }
\ No newline at end of file diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 90ccf689fe88..7972639a97bf 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -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 { diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index c303e77ee00e..099f60152c65 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -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) { diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 8d5e338f8db0..9e8b0d36b5b2 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -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); + } + } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index c909a54630f9..737ffda77c93 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -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; @@ -248,6 +258,27 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** + * 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; @@ -311,6 +344,54 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** + * 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. */ public boolean isConnected() { @@ -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 { diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 9d8516757626..87665e103fcc 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -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); } /** @@ -703,6 +713,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) != PackageManager.PERMISSION_GRANTED) { @@ -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)) { diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index 56236f883e53..803dee296fb2 100644 --- a/services/core/java/com/android/server/media/MediaSessionStack.java +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -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; diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java index c1fa74f5a7a3..d6f811820e19 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java @@ -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(); |