summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt7
-rw-r--r--media/java/android/media/AudioManager.java32
-rw-r--r--media/java/android/media/AudioService.java6
-rw-r--r--media/java/android/media/IRemoteVolumeController.aidl32
-rw-r--r--media/java/android/media/IVolumeController.aidl9
-rw-r--r--media/java/android/media/MediaFocusControl.java47
-rw-r--r--media/java/android/media/VolumeController.java31
-rw-r--r--media/java/android/media/session/ISessionControllerCallback.aidl2
-rw-r--r--media/java/android/media/session/ISessionManager.aidl4
-rw-r--r--media/java/android/media/session/MediaController.java66
-rw-r--r--media/java/android/media/session/MediaSession.java8
-rw-r--r--media/java/android/media/session/MediaSessionManager.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java145
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java107
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java109
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java55
-rw-r--r--services/core/java/com/android/server/media/MediaSessionStack.java13
18 files changed, 453 insertions, 237 deletions
diff --git a/Android.mk b/Android.mk
index d4bb04a..d504481 100644
--- a/Android.mk
+++ b/Android.mk
@@ -306,6 +306,7 @@ LOCAL_SRC_FILES += \
media/java/android/media/IRemoteControlDisplay.aidl \
media/java/android/media/IRemoteDisplayCallback.aidl \
media/java/android/media/IRemoteDisplayProvider.aidl \
+ media/java/android/media/IRemoteVolumeController.aidl \
media/java/android/media/IRemoteVolumeObserver.aidl \
media/java/android/media/IRingtonePlayer.aidl \
media/java/android/media/IVolumeController.aidl \
diff --git a/api/current.txt b/api/current.txt
index 5e48e37..d9d17df 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -15751,6 +15751,7 @@ package android.media.session {
public final class MediaController {
method public void addCallback(android.media.session.MediaController.Callback);
method public void addCallback(android.media.session.MediaController.Callback, android.os.Handler);
+ method public void adjustVolumeBy(int, int);
method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
method public static android.media.session.MediaController fromToken(android.media.session.MediaSessionToken);
method public android.media.MediaMetadata getMetadata();
@@ -15760,6 +15761,7 @@ package android.media.session {
method public android.media.session.MediaController.VolumeInfo getVolumeInfo();
method public void removeCallback(android.media.session.MediaController.Callback);
method public void sendControlCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+ method public void setVolumeTo(int, int);
}
public static abstract class MediaController.Callback {
@@ -15767,6 +15769,7 @@ package android.media.session {
method public void onMetadataChanged(android.media.MediaMetadata);
method public void onPlaybackStateChanged(android.media.session.PlaybackState);
method public void onSessionEvent(java.lang.String, android.os.Bundle);
+ method public void onVolumeInfoChanged(android.media.session.MediaController.VolumeInfo);
}
public final class MediaController.TransportControls {
@@ -15809,8 +15812,8 @@ package android.media.session {
method public void setPlaybackToRemote(android.media.VolumeProvider);
field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
- field public static final int VOLUME_TYPE_LOCAL = 1; // 0x1
- field public static final int VOLUME_TYPE_REMOTE = 2; // 0x2
+ field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
}
public static abstract class MediaSession.Callback {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index fb19242..c8b51e0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2811,38 +2811,6 @@ public class AudioManager {
* Only useful for volume controllers.
* @hide
*/
- public int getRemoteStreamVolume() {
- // TODO STOPSHIP switch callers to use media sessions instead
- Log.e(TAG, "Need to implement new Remote Volume!");
- return 0;
- }
-
- /**
- * Only useful for volume controllers.
- * @hide
- */
- public int getRemoteStreamMaxVolume() {
- // TODO STOPSHIP switch callers to use media sessions instead
- Log.e(TAG, "Need to implement new Remote Volume!");
- return 0;
- }
-
- /**
- * Only useful for volume controllers.
- * @hide
- */
- public void setRemoteStreamVolume(int index) {
- try {
- getService().setRemoteStreamVolume(index);
- } catch (RemoteException e) {
- Log.w(TAG, "Error setting remote stream volume", e);
- }
- }
-
- /**
- * Only useful for volume controllers.
- * @hide
- */
public boolean isStreamAffectedByRingerMode(int streamType) {
try {
return getService().isStreamAffectedByRingerMode(streamType);
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 0c224a6..1588fe9 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -849,10 +849,8 @@ public class AudioService extends IAudioService.Stub {
}
if (streamType == STREAM_REMOTE_MUSIC) {
- // don't play sounds for remote
- flags &= ~(AudioManager.FLAG_PLAY_SOUND|AudioManager.FLAG_FIXED_VOLUME);
- //if (DEBUG_VOL) Log.i(TAG, "Need to adjust remote volume: calling adjustRemoteVolume()");
- mMediaFocusControl.adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, flags);
+ // TODO bounce it to MediaSessionService to find an appropriate
+ // session
} else {
adjustStreamVolume(streamType, direction, flags, callingPackage);
}
diff --git a/media/java/android/media/IRemoteVolumeController.aidl b/media/java/android/media/IRemoteVolumeController.aidl
new file mode 100644
index 0000000..e4a4a42
--- /dev/null
+++ b/media/java/android/media/IRemoteVolumeController.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.session.ISessionController;
+
+/**
+ * AIDL for the MediaSessionService to report interesting events on remote playback
+ * to a volume control dialog. See also IVolumeController for the AudioService half.
+ * TODO add in better support for multiple remote sessions.
+ * @hide
+ */
+oneway interface IRemoteVolumeController {
+ void remoteVolumeChanged(ISessionController session, int flags);
+ // sets the default session to use with the slider, replaces remoteSliderVisibility
+ // on IVolumeController
+ void updateRemoteController(ISessionController session);
+}
diff --git a/media/java/android/media/IVolumeController.aidl b/media/java/android/media/IVolumeController.aidl
index 35d7708..e3593a6 100644
--- a/media/java/android/media/IVolumeController.aidl
+++ b/media/java/android/media/IVolumeController.aidl
@@ -16,17 +16,12 @@
package android.media;
-
/**
- * AIDL for the AudioService to report interesting events to a remote volume control dialog.
+ * AIDL for the AudioService to report interesting events to a volume control
+ * dialog in another process.
* @hide
*/
oneway interface IVolumeController {
- void hasNewRemotePlaybackInfo();
-
- void remoteVolumeChanged(int streamType, int flags);
-
- void remoteSliderVisibility(boolean visible);
void displaySafeVolumeWarning(int flags);
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index a4a7c4e..1587b11 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -2061,29 +2061,6 @@ public class MediaFocusControl implements OnFinished {
}
}
- protected 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
- mVolumeController.postRemoteVolumeChanged(streamType, flags);
- }
-
private void sendVolumeUpdateToRemote(int rccId, int direction) {
if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
if (direction == 0) {
@@ -2183,27 +2160,9 @@ public class MediaFocusControl implements OnFinished {
}
private void onReevaluateRemote() {
- if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); }
- // is there a registered RemoteControlClient that is handling remote playback
- boolean hasRemotePlayback = false;
- synchronized (mPRStack) {
- // iteration stops when PLAYBACK_TYPE_REMOTE is found, so remote control stack
- // traversal order doesn't matter
- Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- PlayerRecord prse = stackIterator.next();
- if (prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
- hasRemotePlayback = true;
- break;
- }
- }
- }
- synchronized (mMainRemote) {
- if (mHasRemotePlayback != hasRemotePlayback) {
- mHasRemotePlayback = hasRemotePlayback;
- mVolumeController.postRemoteSliderVisibility(hasRemotePlayback);
- }
- }
+ // TODO This was used to notify VolumePanel if there was remote playback
+ // in the stack. This is now in MediaSessionService. More code should be
+ // removed.
}
}
diff --git a/media/java/android/media/VolumeController.java b/media/java/android/media/VolumeController.java
index 6b70cc3..d1c51c5 100644
--- a/media/java/android/media/VolumeController.java
+++ b/media/java/android/media/VolumeController.java
@@ -23,7 +23,9 @@ import android.util.Log;
import java.util.Objects;
/**
- * Wraps the remote volume controller interface as a convenience to audio service.
+ * Wraps the volume controller binder interface as a convenience to audio
+ * service.
+ *
* @hide
*/
public class VolumeController {
@@ -52,33 +54,6 @@ public class VolumeController {
return "VolumeController(" + asBinder() + ")";
}
- public void postHasNewRemotePlaybackInfo() {
- if (mController == null) return;
- try {
- mController.hasNewRemotePlaybackInfo();
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling hasNewRemotePlaybackInfo", e);
- }
- }
-
- public void postRemoteVolumeChanged(int streamType, int flags) {
- if (mController == null) return;
- try {
- mController.remoteVolumeChanged(streamType, flags);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling remoteVolumeChanged", e);
- }
- }
-
- public void postRemoteSliderVisibility(boolean visible) {
- if (mController == null) return;
- try {
- mController.remoteSliderVisibility(visible);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling remoteSliderVisibility", e);
- }
- }
-
public void postDisplaySafeVolumeWarning(int flags) {
if (mController == null) return;
try {
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index e823153..baa1379 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -17,6 +17,7 @@ package android.media.session;
import android.media.MediaMetadata;
import android.media.session.RouteInfo;
+import android.media.session.ParcelableVolumeInfo;
import android.media.session.PlaybackState;
import android.os.Bundle;
@@ -30,4 +31,5 @@ oneway interface ISessionControllerCallback {
// These callbacks are for the TransportController
void onPlaybackStateChanged(in PlaybackState state);
void onMetadataChanged(in MediaMetadata metadata);
+ void onVolumeInfoChanged(in ParcelableVolumeInfo info);
} \ 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 bd1fa85..dce84d4 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -16,6 +16,7 @@
package android.media.session;
import android.content.ComponentName;
+import android.media.IRemoteVolumeController;
import android.media.session.IActiveSessionsListener;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
@@ -34,4 +35,7 @@ interface ISessionManager {
void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
int userId);
void removeSessionsListener(in IActiveSessionsListener listener);
+
+ // This is for the system volume UI only
+ void setRemoteVolumeController(in IRemoteVolumeController rvc);
} \ No newline at end of file
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index cc3db26..7653e5a 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -18,6 +18,7 @@ package android.media.session;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
@@ -52,6 +53,7 @@ public final class MediaController {
private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
private static final int MSG_UPDATE_METADATA = 3;
private static final int MSG_ROUTE = 4;
+ private static final int MSG_UPDATE_VOLUME = 5;
private final ISessionController mSessionBinder;
@@ -203,6 +205,43 @@ public final class MediaController {
}
/**
+ * Set the volume of the stream or output this session is playing on. The
+ * command will be ignored if it does not support
+ * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
+ * {@link AudioManager} may be used to affect the handling.
+ *
+ * @see #getVolumeInfo()
+ * @param value The value to set it to, between 0 and the reported max.
+ * @param flags Any flags to pass with the command.
+ */
+ public void setVolumeTo(int value, int flags) {
+ try {
+ mSessionBinder.setVolumeTo(value, flags);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling setVolumeTo.", e);
+ }
+ }
+
+ /**
+ * Adjust the volume of the stream or output this session is playing on.
+ * Negative values will lower the volume. The command will be ignored if it
+ * does not support {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
+ * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
+ * {@link AudioManager} may be used to affect the handling.
+ *
+ * @see #getVolumeInfo()
+ * @param delta The number of steps to adjust the volume by.
+ * @param flags Any flags to pass with the command.
+ */
+ public void adjustVolumeBy(int delta, int flags) {
+ try {
+ mSessionBinder.adjustVolumeBy(delta, flags);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
+ }
+ }
+
+ /**
* Adds a callback to receive updates from the Session. Updates will be
* posted on the caller's thread.
*
@@ -406,6 +445,14 @@ public final class MediaController {
*/
public void onMetadataChanged(@Nullable MediaMetadata metadata) {
}
+
+ /**
+ * Override to handle changes to the volume info.
+ *
+ * @param info The current volume info for this session.
+ */
+ public void onVolumeInfoChanged(VolumeInfo info) {
+ }
}
/**
@@ -552,8 +599,8 @@ public final class MediaController {
/**
* Get the type of volume handling, either local or remote. One of:
* <ul>
- * <li>{@link MediaSession#VOLUME_TYPE_LOCAL}</li>
- * <li>{@link MediaSession#VOLUME_TYPE_REMOTE}</li>
+ * <li>{@link MediaSession#PLAYBACK_TYPE_LOCAL}</li>
+ * <li>{@link MediaSession#PLAYBACK_TYPE_REMOTE}</li>
* </ul>
*
* @return The type of volume handling this session is using.
@@ -564,7 +611,7 @@ public final class MediaController {
/**
* Get the stream this is currently controlling volume on. When the volume
- * type is {@link MediaSession#VOLUME_TYPE_REMOTE} this value does not
+ * type is {@link MediaSession#PLAYBACK_TYPE_REMOTE} this value does not
* have meaning and should be ignored.
*
* @return The stream this session is playing on.
@@ -646,6 +693,16 @@ public final class MediaController {
}
}
+ @Override
+ public void onVolumeInfoChanged(ParcelableVolumeInfo pvi) {
+ MediaController controller = mController.get();
+ if (controller != null) {
+ VolumeInfo info = new VolumeInfo(pvi.volumeType, pvi.audioStream, pvi.controlType,
+ pvi.maxVolume, pvi.currentVolume);
+ controller.postMessage(MSG_UPDATE_VOLUME, info, null);
+ }
+ }
+
}
private final static class MessageHandler extends Handler {
@@ -671,6 +728,9 @@ public final class MediaController {
case MSG_UPDATE_METADATA:
mCallback.onMetadataChanged((MediaMetadata) msg.obj);
break;
+ case MSG_UPDATE_VOLUME:
+ mCallback.onVolumeInfoChanged((VolumeInfo) msg.obj);
+ break;
}
}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 8909d55..7637ec8 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -127,12 +127,12 @@ public final class MediaSession {
/**
* The session uses local playback.
*/
- public static final int VOLUME_TYPE_LOCAL = 1;
+ public static final int PLAYBACK_TYPE_LOCAL = 1;
/**
* The session uses remote playback.
*/
- public static final int VOLUME_TYPE_REMOTE = 2;
+ public static final int PLAYBACK_TYPE_REMOTE = 2;
private final Object mLock = new Object();
@@ -265,7 +265,7 @@ public final class MediaSession {
*/
public void setPlaybackToLocal(int stream) {
try {
- mBinder.configureVolumeHandling(VOLUME_TYPE_LOCAL, stream, 0);
+ mBinder.configureVolumeHandling(PLAYBACK_TYPE_LOCAL, stream, 0);
} catch (RemoteException e) {
Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
}
@@ -294,7 +294,7 @@ public final class MediaSession {
});
try {
- mBinder.configureVolumeHandling(VOLUME_TYPE_REMOTE, volumeProvider.getVolumeControl(),
+ mBinder.configureVolumeHandling(PLAYBACK_TYPE_REMOTE, volumeProvider.getVolumeControl(),
volumeProvider.getMaxVolume());
} catch (RemoteException e) {
Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 882453f..9291bb0 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.media.IRemoteVolumeController;
import android.media.session.ISessionManager;
import android.os.IBinder;
import android.os.RemoteException;
@@ -215,6 +216,21 @@ public final class MediaSessionManager {
}
/**
+ * Set the remote volume controller to receive volume updates on. Only for
+ * use by system UI.
+ *
+ * @param rvc The volume controller to receive updates on.
+ * @hide
+ */
+ public void setRemoteVolumeController(IRemoteVolumeController rvc) {
+ try {
+ mService.setRemoteVolumeController(rvc);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in setRemoteVolumeController.", e);
+ }
+ }
+
+ /**
* Send a media key event. The receiver will be selected automatically.
*
* @param keyEvent The KeyEvent to send.
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 1cab7ea..d514c99 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -34,6 +34,9 @@ import android.media.AudioService;
import android.media.AudioSystem;
import android.media.RingtoneManager;
import android.media.ToneGenerator;
+import android.media.VolumeProvider;
+import android.media.session.MediaController;
+import android.media.session.MediaController.VolumeInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -224,6 +227,7 @@ public class VolumePanel extends Handler {
/** Object that contains data for each slider */
private class StreamControl {
int streamType;
+ MediaController controller;
ViewGroup group;
ImageView icon;
SeekBar seekbarView;
@@ -405,7 +409,8 @@ public class VolumePanel extends Handler {
if (streamType == STREAM_MASTER) {
return mAudioManager.isMasterMute();
} else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- return (mAudioManager.getRemoteStreamVolume() <= 0);
+ // TODO do we need to support a distinct mute property for remote?
+ return false;
} else {
return mAudioManager.isStreamMute(streamType);
}
@@ -415,7 +420,14 @@ public class VolumePanel extends Handler {
if (streamType == STREAM_MASTER) {
return mAudioManager.getMasterMaxVolume();
} else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- return mAudioManager.getRemoteStreamMaxVolume();
+ if (mStreamControls != null) {
+ StreamControl sc = mStreamControls.get(streamType);
+ if (sc != null && sc.controller != null) {
+ VolumeInfo vi = sc.controller.getVolumeInfo();
+ return vi.getMaxVolume();
+ }
+ }
+ return -1;
} else {
return mAudioManager.getStreamMaxVolume(streamType);
}
@@ -425,19 +437,32 @@ public class VolumePanel extends Handler {
if (streamType == STREAM_MASTER) {
return mAudioManager.getMasterVolume();
} else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- return mAudioManager.getRemoteStreamVolume();
+ if (mStreamControls != null) {
+ StreamControl sc = mStreamControls.get(streamType);
+ if (sc != null && sc.controller != null) {
+ VolumeInfo vi = sc.controller.getVolumeInfo();
+ return vi.getCurrentVolume();
+ }
+ }
+ return -1;
} else {
return mAudioManager.getStreamVolume(streamType);
}
}
- private void setStreamVolume(int streamType, int index, int flags) {
- if (streamType == STREAM_MASTER) {
- mAudioManager.setMasterVolume(index, flags);
- } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- mAudioManager.setRemoteStreamVolume(index);
- } else {
- mAudioManager.setStreamVolume(streamType, index, flags);
+ private void setStreamVolume(StreamControl sc, int index, int flags) {
+ if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
+ if (sc.controller != null) {
+ sc.controller.setVolumeTo(index, flags);
+ } else {
+ Log.wtf(mTag, "Adjusting remote volume without a controller!");
+ }
+ } else if (getStreamVolume(sc.streamType) != index) {
+ if (sc.streamType == STREAM_MASTER) {
+ mAudioManager.setMasterVolume(index, flags);
+ } else {
+ mAudioManager.setStreamVolume(sc.streamType, index, flags);
+ }
}
}
@@ -549,7 +574,7 @@ public class VolumePanel extends Handler {
if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
// never disable touch interactions for remote playback, the muting is not tied to
// the state of the phone.
- sc.seekbarView.setEnabled(true);
+ sc.seekbarView.setEnabled(!fixedVolume);
} else if (fixedVolume ||
(sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
(sConfirmSafeVolumeDialog != null)) {
@@ -677,7 +702,7 @@ public class VolumePanel extends Handler {
obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
}
- public void postRemoteVolumeChanged(int streamType, int flags) {
+ public void postRemoteVolumeChanged(MediaController controller, int flags) {
if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
synchronized (this) {
if (mStreamControls == null) {
@@ -685,7 +710,7 @@ public class VolumePanel extends Handler {
}
}
removeMessages(MSG_FREE_RESOURCES);
- obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget();
+ obtainMessage(MSG_REMOTE_VOLUME_CHANGED, flags, 0, controller).sendToTarget();
}
public void postRemoteSliderVisibility(boolean visible) {
@@ -758,7 +783,7 @@ public class VolumePanel extends Handler {
if (mActiveStreamType != streamType) {
reorderSliders(streamType);
}
- onShowVolumeChanged(streamType, flags);
+ onShowVolumeChanged(streamType, flags, null);
}
}
@@ -790,7 +815,7 @@ public class VolumePanel extends Handler {
onVolumeChanged(streamType, flags);
}
- protected void onShowVolumeChanged(int streamType, int flags) {
+ protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) {
int index = getStreamVolume(streamType);
mRingIsSilent = false;
@@ -803,6 +828,7 @@ public class VolumePanel extends Handler {
// get max volume for progress bar
int max = getStreamMaxVolume(streamType);
+ StreamControl sc = mStreamControls.get(streamType);
switch (streamType) {
@@ -865,13 +891,37 @@ public class VolumePanel extends Handler {
}
case AudioService.STREAM_REMOTE_MUSIC: {
+ if (controller == null && sc != null) {
+ // If we weren't passed one try using the last one set.
+ controller = sc.controller;
+ }
+ if (controller == null) {
+ // We still don't have one, ignore the command.
+ Log.w(mTag, "sent remote volume change without a controller!");
+ } else {
+ VolumeInfo vi = controller.getVolumeInfo();
+ index = vi.getCurrentVolume();
+ max = vi.getMaxVolume();
+ if ((vi.getVolumeControl() & VolumeProvider.VOLUME_CONTROL_FIXED) != 0) {
+ // if the remote volume is fixed add the flag for the UI
+ flags |= AudioManager.FLAG_FIXED_VOLUME;
+ }
+ }
if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); }
break;
}
}
- StreamControl sc = mStreamControls.get(streamType);
if (sc != null) {
+ if (streamType == AudioService.STREAM_REMOTE_MUSIC && controller != sc.controller) {
+ if (sc.controller != null) {
+ sc.controller.removeCallback(mMediaControllerCb);
+ }
+ sc.controller = controller;
+ if (controller != null) {
+ sc.controller.addCallback(mMediaControllerCb);
+ }
+ }
if (sc.seekbarView.getMax() != max) {
sc.seekbarView.setMax(max);
}
@@ -949,34 +999,21 @@ public class VolumePanel extends Handler {
mVibrator.vibrate(VIBRATE_DURATION, AudioManager.STREAM_SYSTEM);
}
- protected void onRemoteVolumeChanged(int streamType, int flags) {
- // streamType is the real stream type being affected, but for the UI sliders, we
- // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
- // stream type.
- if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
+ protected void onRemoteVolumeChanged(MediaController controller, int flags) {
+ if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: " + flags
+ + ")");
if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) {
synchronized (this) {
if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
}
- onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
+ onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags, controller);
}
} else {
if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
}
- if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
- removeMessages(MSG_PLAY_SOUND);
- sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
- }
-
- if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
- removeMessages(MSG_PLAY_SOUND);
- removeMessages(MSG_VIBRATE);
- onStopSounds();
- }
-
removeMessages(MSG_FREE_RESOURCES);
sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
resetTimeout();
@@ -987,10 +1024,24 @@ public class VolumePanel extends Handler {
if (isShowing()
&& (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
&& (mStreamControls != null)) {
- onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
+ onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0, null);
}
}
+ /**
+ * Clear the current remote stream controller.
+ */
+ private void clearRemoteStreamController() {
+ if (mStreamControls != null) {
+ StreamControl sc = mStreamControls.get(AudioService.STREAM_REMOTE_MUSIC);
+ if (sc != null) {
+ if (sc.controller != null) {
+ sc.controller.removeCallback(mMediaControllerCb);
+ sc.controller = null;
+ }
+ }
+ }
+ }
/**
* Handler for MSG_SLIDER_VISIBILITY_CHANGED
@@ -1137,6 +1188,7 @@ public class VolumePanel extends Handler {
if (isShowing()) {
if (mDialog != null) {
mDialog.dismiss();
+ clearRemoteStreamController();
mActiveStreamType = -1;
}
}
@@ -1155,7 +1207,7 @@ public class VolumePanel extends Handler {
}
case MSG_REMOTE_VOLUME_CHANGED: {
- onRemoteVolumeChanged(msg.arg1, msg.arg2);
+ onRemoteVolumeChanged((MediaController) msg.obj, msg.arg1);
break;
}
@@ -1202,9 +1254,7 @@ public class VolumePanel extends Handler {
final Object tag = seekBar.getTag();
if (fromUser && tag instanceof StreamControl) {
StreamControl sc = (StreamControl) tag;
- if (getStreamVolume(sc.streamType) != progress) {
- setStreamVolume(sc.streamType, progress, 0);
- }
+ setStreamVolume(sc, progress, 0);
}
resetTimeout();
}
@@ -1215,19 +1265,6 @@ public class VolumePanel extends Handler {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
- final Object tag = seekBar.getTag();
- if (tag instanceof StreamControl) {
- StreamControl sc = (StreamControl) tag;
- // Because remote volume updates are asynchronous, AudioService
- // might have received a new remote volume value since the
- // finger adjusted the slider. So when the progress of the
- // slider isn't being tracked anymore, adjust the slider to the
- // last "published" remote volume value, so the UI reflects the
- // actual volume.
- if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
- seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
- }
- }
}
};
@@ -1257,4 +1294,10 @@ public class VolumePanel extends Handler {
postZenModeChanged(zen);
}
};
+
+ private final MediaController.Callback mMediaControllerCb = new MediaController.Callback() {
+ public void onVolumeInfoChanged(VolumeInfo info) {
+ onRemoteVolumeUpdateIfShown();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 7da90d8..c1f92ff 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -5,7 +5,11 @@ import android.content.Intent;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioManager;
+import android.media.IRemoteVolumeController;
import android.media.IVolumeController;
+import android.media.session.ISessionController;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
import android.net.Uri;
import android.os.Handler;
import android.os.RemoteException;
@@ -42,12 +46,21 @@ public class VolumeUI extends SystemUI {
private final Handler mHandler = new Handler();
private AudioManager mAudioManager;
+ private MediaSessionManager mMediaSessionManager;
private VolumeController mVolumeController;
+ private RemoteVolumeController mRemoteVolumeController;
+
+ private VolumePanel mDialogPanel;
+ private VolumePanel mPanel;
@Override
public void start() {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- mVolumeController = new VolumeController(mContext);
+ mMediaSessionManager = (MediaSessionManager) mContext
+ .getSystemService(Context.MEDIA_SESSION_SERVICE);
+ initPanel();
+ mVolumeController = new VolumeController();
+ mRemoteVolumeController = new RemoteVolumeController();
putComponent(VolumeComponent.class, mVolumeController);
updateController();
mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver);
@@ -57,12 +70,32 @@ public class VolumeUI extends SystemUI {
if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) {
Log.d(TAG, "Registering volume controller");
mAudioManager.setVolumeController(mVolumeController);
+ mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController);
} else {
Log.d(TAG, "Unregistering volume controller");
mAudioManager.setVolumeController(null);
+ mMediaSessionManager.setRemoteVolumeController(null);
}
}
+ private void initPanel() {
+ mPanel = new VolumePanel(mContext, null, new ZenModeControllerImpl(mContext, mHandler));
+ final int delay = mContext.getResources().getInteger(R.integer.feedback_start_delay);
+ mPanel.setZenModePanelCallback(new ZenModePanel.Callback() {
+ @Override
+ public void onMoreSettings() {
+ mHandler.removeCallbacks(mStartZenSettings);
+ mHandler.postDelayed(mStartZenSettings, delay);
+ }
+
+ @Override
+ public void onInteraction() {
+ mDialogPanel.resetTimeout();
+ }
+ });
+ mDialogPanel = mPanel;
+ }
+
private final ContentObserver mObserver = new ContentObserver(mHandler) {
public void onChange(boolean selfChange, Uri uri) {
if (SETTING_URI.equals(uri)) {
@@ -71,55 +104,18 @@ public class VolumeUI extends SystemUI {
}
};
- /** For now, simply host an unmodified base volume panel in this process. */
- private final class VolumeController extends IVolumeController.Stub implements VolumeComponent {
- private final VolumePanel mDialogPanel;
- private VolumePanel mPanel;
-
- public VolumeController(Context context) {
- mPanel = new VolumePanel(context, null, new ZenModeControllerImpl(mContext, mHandler));
- final int delay = context.getResources().getInteger(R.integer.feedback_start_delay);
- mPanel.setZenModePanelCallback(new ZenModePanel.Callback() {
- @Override
- public void onMoreSettings() {
- mHandler.removeCallbacks(mStartZenSettings);
- mHandler.postDelayed(mStartZenSettings, delay);
- }
-
- @Override
- public void onInteraction() {
- mDialogPanel.resetTimeout();
- }
- });
- mDialogPanel = mPanel;
- }
-
- private final Runnable mStartZenSettings = new Runnable() {
- @Override
- public void run() {
- mDialogPanel.postDismiss();
- final Intent intent = ZenModePanel.ZEN_SETTINGS;
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
- }
- };
-
- @Override
- public void hasNewRemotePlaybackInfo() throws RemoteException {
- mPanel.postHasNewRemotePlaybackInfo();
- }
-
+ private final Runnable mStartZenSettings = new Runnable() {
@Override
- public void remoteVolumeChanged(int streamType, int flags)
- throws RemoteException {
- mPanel.postRemoteVolumeChanged(streamType, flags);
+ public void run() {
+ mDialogPanel.postDismiss();
+ final Intent intent = ZenModePanel.ZEN_SETTINGS;
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
}
+ };
- @Override
- public void remoteSliderVisibility(boolean visible)
- throws RemoteException {
- mPanel.postRemoteSliderVisibility(visible);
- }
+ /** For now, simply host an unmodified base volume panel in this process. */
+ private final class VolumeController extends IVolumeController.Stub implements VolumeComponent {
@Override
public void displaySafeVolumeWarning(int flags) throws RemoteException {
@@ -163,4 +159,21 @@ public class VolumeUI extends SystemUI {
mPanel = panel == null ? mDialogPanel : panel;
}
}
+
+ private final class RemoteVolumeController extends IRemoteVolumeController.Stub {
+
+ @Override
+ public void remoteVolumeChanged(ISessionController binder, int flags)
+ throws RemoteException {
+ MediaController controller = MediaController.fromBinder(binder);
+ mPanel.postRemoteVolumeChanged(controller, flags);
+ }
+
+ @Override
+ public void updateRemoteController(ISessionController session) throws RemoteException {
+ mPanel.postRemoteSliderVisibility(session != null);
+ // TODO stash default session in case the slider can be opened other
+ // than by remoteVolumeChanged.
+ }
+ }
}
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);