diff options
author | RoboErik <epastern@google.com> | 2014-06-23 15:38:48 -0700 |
---|---|---|
committer | RoboErik <epastern@google.com> | 2014-06-25 14:36:51 -0700 |
commit | 19c9518f6a817d53d5234de0020313cab6950b2f (patch) | |
tree | a9235b0a2a4fd973ba22b2dbf0c56e1f72a894de /services/core/java/com/android/server/media | |
parent | 0b16d7d807b213047bb018d565de8190a7362451 (diff) | |
download | frameworks_base-19c9518f6a817d53d5234de0020313cab6950b2f.zip frameworks_base-19c9518f6a817d53d5234de0020313cab6950b2f.tar.gz frameworks_base-19c9518f6a817d53d5234de0020313cab6950b2f.tar.bz2 |
b/15729204 Pipe sessions through to VolumePanel
When remote volume is changed via volume buttons we need to notify
the system UI so it can show the slider. This also passes it the
controller to use so adjustments to the slider are sent back to
the correct session.
Change-Id: If5847bcd5db16c56e0e9904b88c94e5b28954c41
Diffstat (limited to 'services/core/java/com/android/server/media')
3 files changed, 162 insertions, 15 deletions
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 1264741..6f1eb8f 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -16,11 +16,9 @@ package com.android.server.media; -import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.media.routeprovider.RouteRequest; import android.media.session.ISessionController; import android.media.session.ISessionControllerCallback; @@ -49,7 +47,6 @@ import android.os.Message; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; -import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -87,6 +84,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { */ private static final int ACTIVE_BUFFER = 30000; + /** + * The amount of time we'll send an assumed volume after the last volume + * command before reverting to the last reported volume. + */ + private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000; + private final MessageHandler mHandler; private final int mOwnerPid; @@ -122,11 +125,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { // Volume handling fields private AudioManager mAudioManager; - private int mVolumeType = MediaSession.VOLUME_TYPE_LOCAL; + private int mVolumeType = MediaSession.PLAYBACK_TYPE_LOCAL; private int mAudioStream = AudioManager.STREAM_MUSIC; private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE; private int mMaxVolume = 0; private int mCurrentVolume = 0; + private int mOptimisticVolume = -1; // End volume handling fields private boolean mIsActive = false; @@ -276,7 +280,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { * @param delta The amount to adjust the volume by. */ public void adjustVolumeBy(int delta, int flags) { - if (mVolumeType == MediaSession.VOLUME_TYPE_LOCAL) { + if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) { if (delta == 0) { mAudioManager.adjustStreamVolume(mAudioStream, delta, flags); } else { @@ -298,18 +302,46 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return; } mSessionCb.adjustVolumeBy(delta); + + int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume); + mOptimisticVolume = volumeBefore + delta; + mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume)); + mHandler.removeCallbacks(mClearOptimisticVolumeRunnable); + mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT); + if (volumeBefore != mOptimisticVolume) { + pushVolumeUpdate(); + } + + if (DEBUG) { + Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is " + + mMaxVolume); + } } } public void setVolumeTo(int value, int flags) { - if (mVolumeType == MediaSession.VOLUME_TYPE_LOCAL) { + if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) { mAudioManager.setStreamVolume(mAudioStream, value, flags); } else { if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) { // Nothing to do. The volume can't be set directly. return; } + value = Math.max(0, Math.min(value, mMaxVolume)); mSessionCb.setVolumeTo(value); + + int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume); + mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume)); + mHandler.removeCallbacks(mClearOptimisticVolumeRunnable); + mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT); + if (volumeBefore != mOptimisticVolume) { + pushVolumeUpdate(); + } + + if (DEBUG) { + Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is " + + mMaxVolume); + } } } @@ -427,6 +459,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** + * Get the volume we'd like it to be set to. This is only valid for a short + * while after a call to adjust or set volume. + * + * @return The current optimistic volume or -1. + */ + public int getOptimisticVolume() { + return mOptimisticVolume; + } + + /** * @return True if this session is currently connected to a route. */ public boolean isConnected() { @@ -542,8 +584,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { cb.onPlaybackStateChanged(mPlaybackState); } catch (DeadObjectException e) { mControllerCallbacks.remove(i); - Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate. size=" - + mControllerCallbacks.size() + " cb=" + cb, e); + Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate.", e); } catch (RemoteException e) { Log.w(TAG, "unexpected exception in pushPlaybackStateUpdate.", e); } @@ -561,10 +602,29 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { try { cb.onMetadataChanged(mMetadata); } catch (DeadObjectException e) { - Log.w(TAG, "Removing dead callback in pushMetadataUpdate. " + cb, e); + Log.w(TAG, "Removing dead callback in pushMetadataUpdate. ", e); mControllerCallbacks.remove(i); } catch (RemoteException e) { - Log.w(TAG, "unexpected exception in pushMetadataUpdate. " + cb, e); + Log.w(TAG, "unexpected exception in pushMetadataUpdate. ", e); + } + } + } + } + + private void pushVolumeUpdate() { + synchronized (mLock) { + if (mDestroyed) { + return; + } + ParcelableVolumeInfo info = mController.getVolumeAttributes(); + for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { + ISessionControllerCallback cb = mControllerCallbacks.get(i); + try { + cb.onVolumeInfoChanged(info); + } catch (DeadObjectException e) { + Log.w(TAG, "Removing dead callback in pushVolumeUpdate. ", e); + } catch (RemoteException e) { + Log.w(TAG, "Unexpected exception in pushVolumeUpdate. ", e); } } } @@ -680,6 +740,17 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } }; + private final Runnable mClearOptimisticVolumeRunnable = new Runnable() { + @Override + public void run() { + boolean needUpdate = (mOptimisticVolume != mCurrentVolume); + mOptimisticVolume = -1; + if (needUpdate) { + pushVolumeUpdate(); + } + } + }; + private final class SessionStub extends ISession.Stub { @Override public void destroy() { @@ -785,12 +856,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { @Override public void setCurrentVolume(int volume) { mCurrentVolume = volume; + mHandler.post(MessageHandler.MSG_UPDATE_VOLUME); } @Override public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException { + boolean typeChanged = type != mVolumeType; switch(type) { - case MediaSession.VOLUME_TYPE_LOCAL: + case MediaSession.PLAYBACK_TYPE_LOCAL: mVolumeType = type; int audioStream = arg1; if (isValidStream(audioStream)) { @@ -800,7 +873,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { mAudioStream = AudioManager.STREAM_MUSIC; } break; - case MediaSession.VOLUME_TYPE_REMOTE: + case MediaSession.PLAYBACK_TYPE_REMOTE: mVolumeType = type; mVolumeControlType = arg1; mMaxVolume = arg2; @@ -809,6 +882,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { throw new IllegalArgumentException("Volume handling type " + type + " not recognized."); } + if (typeChanged) { + mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this); + } } private boolean isValidStream(int stream) { @@ -1027,10 +1103,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { int type; int max; int current; - if (mVolumeType == MediaSession.VOLUME_TYPE_REMOTE) { + if (mVolumeType == MediaSession.PLAYBACK_TYPE_REMOTE) { type = mVolumeControlType; max = mMaxVolume; - current = mCurrentVolume; + current = mOptimisticVolume != -1 ? mOptimisticVolume + : mCurrentVolume; } else { type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE; max = mAudioManager.getStreamMaxVolume(mAudioStream); @@ -1130,6 +1207,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private static final int MSG_UPDATE_ROUTE_FILTERS = 5; private static final int MSG_SEND_COMMAND = 6; private static final int MSG_UPDATE_SESSION_STATE = 7; + private static final int MSG_UPDATE_VOLUME = 8; public MessageHandler(Looper looper) { super(looper); @@ -1157,6 +1235,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { case MSG_UPDATE_SESSION_STATE: // TODO add session state break; + case MSG_UPDATE_VOLUME: + pushVolumeUpdate(); + break; } } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index c0b7d68..873ed71 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -27,8 +27,8 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.media.AudioManager; import android.media.IAudioService; +import android.media.IRemoteVolumeController; import android.media.routeprovider.RouteRequest; import android.media.session.IActiveSessionsListener; import android.media.session.ISession; @@ -98,6 +98,10 @@ public class MediaSessionService extends SystemService implements Monitor { // session so we drop late callbacks properly. private int mShowRoutesRequestId = 0; + // Used to notify system UI when remote volume was changed. TODO find a + // better way to handle this. + private IRemoteVolumeController mRvc; + // TODO refactor to have per user state for providers. See // MediaRouterService for an example @@ -225,6 +229,16 @@ public class MediaSessionService extends SystemService implements Monitor { } } + public void onSessionPlaybackTypeChanged(MediaSessionRecord record) { + synchronized (mLock) { + if (!mAllSessions.contains(record)) { + Log.d(TAG, "Unknown session changed playback type. Ignoring."); + return; + } + pushRemoteVolumeUpdateLocked(record.getUserId()); + } + } + @Override public void onStartUser(int userHandle) { updateUser(); @@ -367,6 +381,13 @@ public class MediaSessionService extends SystemService implements Monitor { } } + private void enforceStatusBarPermission(String action, int pid, int uid) { + if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + pid, uid) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Only system ui may " + action); + } + } + /** * This checks if the component is an enabled notification listener for the * specified user. Enabled components may only operate on behalf of the user @@ -497,6 +518,7 @@ public class MediaSessionService extends SystemService implements Monitor { for (int i = 0; i < size; i++) { tokens.add(new MediaSessionToken(records.get(i).getControllerBinder())); } + pushRemoteVolumeUpdateLocked(userId); for (int i = mSessionsListeners.size() - 1; i >= 0; i--) { SessionsListenerRecord record = mSessionsListeners.get(i); if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) { @@ -512,6 +534,17 @@ public class MediaSessionService extends SystemService implements Monitor { } } + private void pushRemoteVolumeUpdateLocked(int userId) { + if (mRvc != null) { + try { + MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId); + mRvc.updateRemoteController(record == null ? null : record.getControllerBinder()); + } catch (RemoteException e) { + Log.wtf(TAG, "Error sending default remote volume to sys ui.", e); + } + } + } + private void persistMediaButtonReceiverLocked(MediaSessionRecord record) { ComponentName receiver = record.getMediaButtonReceiver(); if (receiver != null) { @@ -844,6 +877,19 @@ public class MediaSessionService extends SystemService implements Monitor { } @Override + public void setRemoteVolumeController(IRemoteVolumeController rvc) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + enforceStatusBarPermission("listen for volume changes", pid, uid); + mRvc = rvc; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -929,6 +975,13 @@ public class MediaSessionService extends SystemService implements Monitor { } } else { session.adjustVolumeBy(delta, flags); + if (mRvc != null) { + try { + mRvc.remoteVolumeChanged(session.getControllerBinder(), flags); + } catch (Exception e) { + Log.wtf(TAG, "Error sending volume change to system UI.", e); + } + } } } diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index 144ccfa..e26a2eb 100644 --- a/services/core/java/com/android/server/media/MediaSessionStack.java +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -203,6 +203,19 @@ public class MediaSessionStack { return null; } + public MediaSessionRecord getDefaultRemoteSession(int userId) { + 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.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE) { + return record; + } + } + return null; + } + public void dump(PrintWriter pw, String prefix) { ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0, UserHandle.USER_ALL); |