diff options
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(); |