diff options
18 files changed, 453 insertions, 237 deletions
@@ -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); |