diff options
author | Jean-Michel Trivi <jmtrivi@google.com> | 2012-06-19 10:54:32 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2012-06-19 10:54:32 -0700 |
commit | 2f4423043ffeaf232ec984be03743326f08cdc8a (patch) | |
tree | e1ea822923119e84b19c67ff9ec057cee02f2b4d | |
parent | 5bb835a95942b0b313b2fa4e3f05941e630f9be7 (diff) | |
parent | 3114ce3861f20f9a5c2c59dd2629197a1f4874a8 (diff) | |
download | frameworks_base-2f4423043ffeaf232ec984be03743326f08cdc8a.zip frameworks_base-2f4423043ffeaf232ec984be03743326f08cdc8a.tar.gz frameworks_base-2f4423043ffeaf232ec984be03743326f08cdc8a.tar.bz2 |
Merge "Remote volume handling" into jb-dev
-rw-r--r-- | core/java/android/content/Intent.java | 35 | ||||
-rw-r--r-- | core/java/android/view/VolumePanel.java | 172 | ||||
-rw-r--r-- | core/res/res/values/public.xml | 2 | ||||
-rw-r--r-- | media/java/android/media/AudioManager.java | 31 | ||||
-rw-r--r-- | media/java/android/media/AudioService.java | 464 | ||||
-rw-r--r-- | media/java/android/media/IAudioService.aidl | 10 | ||||
-rw-r--r-- | media/java/android/media/RemoteControlClient.java | 261 | ||||
-rw-r--r-- | policy/src/com/android/internal/policy/impl/KeyguardViewBase.java | 17 |
8 files changed, 939 insertions, 53 deletions
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index edd509b..50972e8 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -27,6 +27,7 @@ import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; +import android.media.RemoteControlClient; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -2152,6 +2153,19 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.USB_AUDIO_DEVICE_PLUG"; /** + * @hide (to be un-hidden) + * Broadcast Action: the volume handled by the receiver should be updated based on the + * mutually exclusive extras, {@link #EXTRA_VOLUME_UPDATE_DIRECTION} + * and {@link #EXTRA_VOLUME_UPDATE_VALUE}. + * + * @see #EXTRA_VOLUME_UPDATE_DIRECTION + * @see #EXTRA_VOLUME_UPDATE_VALUE + * @see android.media.RemoteControlClient + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_VOLUME_UPDATE = "android.intent.action.VOLUME_UPDATE"; + + /** * <p>Broadcast Action: The user has switched on advanced settings in the settings app:</p> * <ul> * <li><em>state</em> - A boolean value indicating whether the settings is on or off.</li> @@ -2839,6 +2853,27 @@ public class Intent implements Parcelable, Cloneable { */ public static final String EXTRA_USERID = "android.intent.extra.user_id"; + + /** + * @hide (to be un-hidden) + * An integer indicating whether the volume is to be increased (positive value) or decreased + * (negative value). For bundled changes, the absolute value indicates the number of changes + * in the same direction, e.g. +3 corresponds to three "volume up" changes. + * @see #ACTION_VOLUME_UPDATE + */ + public static final String EXTRA_VOLUME_UPDATE_DIRECTION = + "android.intent.extra.VOLUME_UPDATE_DIRECTION"; + + /** + * @hide (to be un-hidden) + * An integer indicating the new volume value, always between 0 and the value set for + * {@link RemoteControlClient#PLAYBACKINFO_VOLUME_MAX} with + * {@link RemoteControlClient#setPlaybackInformation(int, int)} + * @see #ACTION_VOLUME_UPDATE + */ + public static final String EXTRA_VOLUME_UPDATE_VALUE = + "android.intent.extra.VOLUME_UPDATE_VALUE"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java index 5ffc2c3..cf9bcdd 100644 --- a/core/java/android/view/VolumePanel.java +++ b/core/java/android/view/VolumePanel.java @@ -34,10 +34,7 @@ import android.media.ToneGenerator; import android.net.Uri; import android.os.Handler; import android.os.Message; -import android.os.RemoteException; import android.os.Vibrator; -import android.provider.Settings; -import android.provider.Settings.System; import android.util.Log; import android.view.WindowManager.LayoutParams; import android.widget.ImageView; @@ -92,9 +89,13 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private static final int MSG_TIMEOUT = 5; private static final int MSG_RINGER_MODE_CHANGED = 6; private static final int MSG_MUTE_CHANGED = 7; + private static final int MSG_REMOTE_VOLUME_CHANGED = 8; + private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9; + private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10; // Pseudo stream type for master volume private static final int STREAM_MASTER = -100; + // Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC protected Context mContext; private AudioManager mAudioManager; @@ -155,10 +156,15 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie true), // for now, use media resources for master volume MasterStream(STREAM_MASTER, - R.string.volume_icon_description_media, + R.string.volume_icon_description_media, //FIXME should have its own description R.drawable.ic_audio_vol, R.drawable.ic_audio_vol_mute, - false); + false), + RemoteStream(AudioService.STREAM_REMOTE_MUSIC, + R.string.volume_icon_description_media, //FIXME should have its own description + R.drawable.ic_media_route_on_holo_dark, + R.drawable.ic_media_route_disabled_holo_dark, + false);// will be dynamically updated int streamType; int descRes; @@ -184,7 +190,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie StreamResources.MediaStream, StreamResources.NotificationStream, StreamResources.AlarmStream, - StreamResources.MasterStream + StreamResources.MasterStream, + StreamResources.RemoteStream }; /** Object that contains data for each slider */ @@ -297,6 +304,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private boolean isMuted(int streamType) { if (streamType == STREAM_MASTER) { return mAudioManager.isMasterMute(); + } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { + return (mAudioService.getRemoteStreamVolume() <= 0); } else { return mAudioManager.isStreamMute(streamType); } @@ -305,6 +314,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private int getStreamMaxVolume(int streamType) { if (streamType == STREAM_MASTER) { return mAudioManager.getMasterMaxVolume(); + } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { + return mAudioService.getRemoteStreamMaxVolume(); } else { return mAudioManager.getStreamMaxVolume(streamType); } @@ -313,6 +324,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private int getStreamVolume(int streamType) { if (streamType == STREAM_MASTER) { return mAudioManager.getMasterVolume(); + } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { + return mAudioService.getRemoteStreamVolume(); } else { return mAudioManager.getStreamVolume(streamType); } @@ -321,6 +334,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private void setStreamVolume(int streamType, int index, int flags) { if (streamType == STREAM_MASTER) { mAudioManager.setMasterVolume(index, flags); + } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { + mAudioService.setRemoteStreamVolume(index); } else { mAudioManager.setStreamVolume(streamType, index, flags); } @@ -398,7 +413,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) { sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate); } - if (sc.streamType != mAudioManager.getMasterStreamType() && muted) { + 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); + } else if (sc.streamType != mAudioManager.getMasterStreamType() && muted) { sc.seekbarView.setEnabled(false); } else { sc.seekbarView.setEnabled(true); @@ -446,6 +465,40 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget(); } + public void postRemoteVolumeChanged(int streamType, int flags) { + if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return; + synchronized (this) { + if (mStreamControls == null) { + createSliders(); + } + } + removeMessages(MSG_FREE_RESOURCES); + obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget(); + } + + public void postRemoteSliderVisibility(boolean visible) { + obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED, + AudioService.STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget(); + } + + /** + * Called by AudioService when it has received new remote playback information that + * would affect the VolumePanel display (mainly volumes). The difference with + * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message + * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being + * displayed. + * This special code path is due to the fact that remote volume updates arrive to AudioService + * asynchronously. So after AudioService has sent the volume update (which should be treated + * as a request to update the volume), the application will likely set a new volume. If the UI + * is still up, we need to refresh the display to show this new value. + */ + public void postHasNewRemotePlaybackInfo() { + if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return; + // don't create or prevent resources to be freed, if they disappear, this update came too + // late and shouldn't warrant the panel to be displayed longer + obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget(); + } + public void postMasterVolumeChanged(int flags) { postVolumeChanged(STREAM_MASTER, flags); } @@ -585,6 +638,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie max++; break; } + + case AudioService.STREAM_REMOTE_MUSIC: { + if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); } + break; + } } StreamControl sc = mStreamControls.get(streamType); @@ -593,7 +651,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie sc.seekbarView.setMax(max); } sc.seekbarView.setProgress(index); - if (streamType != mAudioManager.getMasterStreamType() && isMuted(streamType)) { + if (streamType != mAudioManager.getMasterStreamType() + && streamType != AudioService.STREAM_REMOTE_MUSIC && isMuted(streamType)) { sc.seekbarView.setEnabled(false); } else { sc.seekbarView.setEnabled(true); @@ -601,7 +660,9 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie } if (!mDialog.isShowing()) { - mAudioManager.forceVolumeControlStream(streamType); + int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType; + // when the stream is for remote playback, use -1 to reset the stream type evaluation + mAudioManager.forceVolumeControlStream(stream); mDialog.setContentView(mView); // Showing dialog - use collapsed state if (mShowCombinedVolumes) { @@ -611,7 +672,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie } // Do a little vibrate if applicable (only when going into vibrate mode) - if ((flags & AudioManager.FLAG_VIBRATE) != 0 && + if ((streamType != AudioService.STREAM_REMOTE_MUSIC) && + ((flags & AudioManager.FLAG_VIBRATE) != 0) && mAudioService.isStreamAffectedByRingerMode(streamType) && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) { sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); @@ -658,6 +720,72 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie mVibrator.vibrate(VIBRATE_DURATION); } + 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(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")"); + + if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) { + synchronized (this) { + if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) { + reorderSliders(AudioService.STREAM_REMOTE_MUSIC); + } + onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags); + } + } else { + if (LOGD) Log.d(TAG, "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(); + } + + protected void onRemoteVolumeUpdateIfShown() { + if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()"); + if (mDialog.isShowing() + && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC) + && (mStreamControls != null)) { + onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0); + } + } + + + /** + * Handler for MSG_SLIDER_VISIBILITY_CHANGED + * Hide or show a slider + * @param streamType can be a valid stream type value, or VolumePanel.STREAM_MASTER, + * or AudioService.STREAM_REMOTE_MUSIC + * @param visible + */ + synchronized protected void onSliderVisibilityChanged(int streamType, int visible) { + if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")"); + boolean isVisible = (visible == 1); + for (int i = STREAMS.length - 1 ; i >= 0 ; i--) { + StreamResources streamRes = STREAMS[i]; + if (streamRes.streamType == streamType) { + streamRes.show = isVisible; + if (!isVisible && (mActiveStreamType == streamType)) { + mActiveStreamType = -1; + } + break; + } + } + } + /** * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. */ @@ -750,6 +878,19 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie } break; } + + case MSG_REMOTE_VOLUME_CHANGED: { + onRemoteVolumeChanged(msg.arg1, msg.arg2); + break; + } + + case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN: + onRemoteVolumeUpdateIfShown(); + break; + + case MSG_SLIDER_VISIBILITY_CHANGED: + onSliderVisibilityChanged(msg.arg1, msg.arg2); + break; } } @@ -779,6 +920,17 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie } 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)); + } + } } public void onClick(View v) { diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 244aec8..946bfe6 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1024,6 +1024,8 @@ <java-symbol type="drawable" name="notification_template_icon_bg" /> <java-symbol type="drawable" name="notification_template_icon_low_bg" /> <java-symbol type="drawable" name="ic_lockscreen_unlock_phantom" /> + <java-symbol type="drawable" name="ic_media_route_on_holo_dark" /> + <java-symbol type="drawable" name="ic_media_route_disabled_holo_dark" /> <java-symbol type="layout" name="action_bar_home" /> <java-symbol type="layout" name="action_bar_title_item" /> diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e88c535..b6e4659 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -98,7 +98,10 @@ public class AudioManager { /** * @hide Broadcast intent when the volume for a particular stream type changes. - * Includes the stream, the new volume and previous volumes + * Includes the stream, the new volume and previous volumes. + * Notes: + * - for internal platform use only, do not make public, + * - never used for "remote" volume changes * * @see #EXTRA_VOLUME_STREAM_TYPE * @see #EXTRA_VOLUME_STREAM_VALUE @@ -1498,6 +1501,24 @@ public class AudioManager { return AudioSystem.isStreamActive(STREAM_MUSIC, 0); } + /** + * @hide + * If the stream is active locally or remotely, adjust its volume according to the enforced + * priority rules. + * Note: only AudioManager.STREAM_MUSIC is supported at the moment + */ + public void adjustLocalOrRemoteStreamVolume(int streamType, int direction) { + if (streamType != STREAM_MUSIC) { + Log.w(TAG, "adjustLocalOrRemoteStreamVolume() doesn't support stream " + streamType); + } + IAudioService service = getService(); + try { + service.adjustLocalOrRemoteStreamVolume(streamType, direction); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in adjustLocalOrRemoteStreamVolume", e); + } + } + /* * Sets a generic audio configuration parameter. The use of these parameters * are platform dependant, see libaudio @@ -2074,10 +2095,12 @@ public class AudioManager { } IAudioService service = getService(); try { - service.registerRemoteControlClient(rcClient.getRcMediaIntent(), /* mediaIntent */ - rcClient.getIRemoteControlClient(), /* rcClient */ + int rcseId = service.registerRemoteControlClient( + rcClient.getRcMediaIntent(), /* mediaIntent */ + rcClient.getIRemoteControlClient(),/* rcClient */ // used to match media button event receiver and audio focus - mContext.getPackageName()); /* packageName */ + mContext.getPackageName()); /* packageName */ + rcClient.setRcseId(rcseId); } catch (RemoteException e) { Log.e(TAG, "Dead object in registerRemoteControlClient"+e); } diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 27dc6e3..418dc52 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -101,6 +101,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** Debug remote control client/display feature */ protected static final boolean DEBUG_RC = false; + /** Debug volumes */ + protected static final boolean DEBUG_VOL = false; /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; @@ -120,7 +122,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** If the msg is already queued, queue this one and leave the old. */ private static final int SENDMSG_QUEUE = 2; - // AudioHandler message.whats + // AudioHandler messages private static final int MSG_SET_DEVICE_VOLUME = 0; private static final int MSG_PERSIST_VOLUME = 1; private static final int MSG_PERSIST_MASTER_VOLUME = 2; @@ -138,11 +140,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private static final int MSG_SET_ALL_VOLUMES = 14; private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 15; private static final int MSG_REPORT_NEW_ROUTES = 16; - // messages handled under wakelock, can only be queued, i.e. sent with queueMsgUnderWakeLock(), + private static final int MSG_REEVALUATE_REMOTE = 17; + // start of messages handled under wakelock + // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) - private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 17; - private static final int MSG_SET_A2DP_CONNECTION_STATE = 18; - + private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 18; + private static final int MSG_SET_A2DP_CONNECTION_STATE = 19; + // end of messages handled under wakelock // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be // persisted @@ -405,6 +409,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers = new RemoteCallbackList<IAudioRoutesObserver>(); + /** + * A fake stream type to match the notion of remote media playback + */ + public final static int STREAM_REMOTE_MUSIC = -200; + /////////////////////////////////////////////////////////////////////////// // Construction /////////////////////////////////////////////////////////////////////////// @@ -488,6 +497,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mMasterVolumeRamp = context.getResources().getIntArray( com.android.internal.R.array.config_masterVolumeRamp); + mMainRemote = new RemotePlaybackState(-1, MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC], + MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC]); + mHasRemotePlayback = false; + mMainRemoteIsActive = false; + postReevaluateRemote(); } private void createAudioSystemThread() { @@ -657,9 +671,20 @@ public class AudioService extends IAudioService.Stub implements OnFinished { adjustSuggestedStreamVolume(direction, AudioManager.USE_DEFAULT_STREAM_TYPE, flags); } + /** @see AudioManager#adjustLocalOrRemoteStreamVolume(int, int) with current assumption + * on streamType: fixed to STREAM_MUSIC */ + public void adjustLocalOrRemoteStreamVolume(int streamType, int direction) { + if (DEBUG_VOL) Log.d(TAG, "adjustLocalOrRemoteStreamVolume(dir="+direction+")"); + if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) { + adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, 0); + } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) { + adjustStreamVolume(AudioSystem.STREAM_MUSIC, direction, 0); + } + } + /** @see AudioManager#adjustVolume(int, int, int) */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) { - + if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream="+suggestedStreamType); int streamType; if (mVolumeControlStream != -1) { streamType = mVolumeControlStream; @@ -668,17 +693,27 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } // Play sounds on STREAM_RING only and if lock screen is not on. - if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && + if ((streamType != STREAM_REMOTE_MUSIC) && + (flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ((mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING) || (mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()))) { flags &= ~AudioManager.FLAG_PLAY_SOUND; } - adjustStreamVolume(streamType, direction, flags); + if (streamType == STREAM_REMOTE_MUSIC) { + // don't play sounds for remote + flags &= ~AudioManager.FLAG_PLAY_SOUND; + //if (DEBUG_VOL) Log.i(TAG, "Need to adjust remote volume: calling adjustRemoteVolume()"); + adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, flags); + } else { + adjustStreamVolume(streamType, direction, flags); + } } /** @see AudioManager#adjustStreamVolume(int, int, int) */ public void adjustStreamVolume(int streamType, int direction, int flags) { + if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream="+streamType+", dir="+direction); + ensureValidDirection(direction); ensureValidStreamType(streamType); @@ -1370,6 +1405,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE); + if (streamType == STREAM_REMOTE_MUSIC) { + // here handle remote media playback the same way as local playback + streamType = AudioManager.STREAM_MUSIC; + } int device = getDeviceForStream(streamType); int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device, false); setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, false); @@ -2169,40 +2208,61 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL..."); return AudioSystem.STREAM_VOICE_CALL; } + } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { + // Having the suggested stream be USE_DEFAULT_STREAM_TYPE is how remote control + // volume can have priority over STREAM_MUSIC + if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) { + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC"); + return STREAM_REMOTE_MUSIC; + } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) { + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active"); + return AudioSystem.STREAM_MUSIC; + } else { + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default"); + return AudioSystem.STREAM_RING; + } } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) { - // Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC..."); + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active"); return AudioSystem.STREAM_MUSIC; - } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { - // Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING..." - // + " b/c USE_DEFAULT_STREAM_TYPE..."); - return AudioSystem.STREAM_RING; } else { - // Log.v(TAG, "getActiveStreamType: Returning suggested type " + suggestedStreamType); + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type " + + suggestedStreamType); return suggestedStreamType; } } else { if (isInCommunication()) { if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) == AudioSystem.FORCE_BT_SCO) { - // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO..."); + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO"); return AudioSystem.STREAM_BLUETOOTH_SCO; } else { - // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL..."); + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL"); return AudioSystem.STREAM_VOICE_CALL; } } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION, - NOTIFICATION_VOLUME_DELAY_MS) || - AudioSystem.isStreamActive(AudioSystem.STREAM_RING, + NOTIFICATION_VOLUME_DELAY_MS) || + AudioSystem.isStreamActive(AudioSystem.STREAM_RING, NOTIFICATION_VOLUME_DELAY_MS)) { - // Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION..."); + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION"); return AudioSystem.STREAM_NOTIFICATION; - } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) || - (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE)) { - // Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC " - // + " b/c USE_DEFAULT_STREAM_TYPE..."); - return AudioSystem.STREAM_MUSIC; + } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { + if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) { + // Having the suggested stream be USE_DEFAULT_STREAM_TYPE is how remote control + // volume can have priority over STREAM_MUSIC + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC"); + return STREAM_REMOTE_MUSIC; + } else { + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: using STREAM_MUSIC as default"); + return AudioSystem.STREAM_MUSIC; + } } else { - // Log.v(TAG, "getActiveStreamType: Returning suggested type " + suggestedStreamType); + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type " + + suggestedStreamType); return suggestedStreamType; } } @@ -3036,6 +3096,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mRoutesObservers.finishBroadcast(); break; } + + case MSG_REEVALUATE_REMOTE: + onReevaluateRemote(); + break; } } } @@ -4103,6 +4167,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // remote control client died, make sure the displays don't use it anymore // by setting its remote control client to null registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/); + // the dead client was maybe handling remote playback, reevaluate + postReevaluateRemote(); } public IBinder getBinder() { @@ -4110,7 +4176,46 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + /** + * A global counter for RemoteControlClient identifiers + */ + private static int sLastRccId = 0; + + private class RemotePlaybackState { + int mRccId; + int mVolume; + int mVolumeMax; + int mVolumeHandling; + + private RemotePlaybackState(int id, int vol, int volMax) { + mRccId = id; + mVolume = vol; + mVolumeMax = volMax; + mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; + } + } + + /** + * Internal cache for the playback information of the RemoteControlClient whose volume gets to + * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack + * every time we need this info. + */ + private RemotePlaybackState mMainRemote; + /** + * Indicates whether the "main" RemoteControlClient is considered active. + * Use synchronized on mMainRemote. + */ + private boolean mMainRemoteIsActive; + /** + * Indicates whether there is remote playback going on. True even if there is no "active" + * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it + * handles remote playback. + * Use synchronized on mMainRemote. + */ + private boolean mHasRemotePlayback; + private static class RemoteControlStackEntry { + public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED; /** * The target for the ACTION_MEDIA_BUTTON events. * Always non null. @@ -4129,6 +4234,24 @@ public class AudioService extends IAudioService.Stub implements OnFinished { * but no remote control client has been registered) */ public IRemoteControlClient mRcClient; public RcClientDeathHandler mRcClientDeathHandler; + /** + * Information only used for non-local playback + */ + public int mPlaybackType; + public int mPlaybackVolume; + public int mPlaybackVolumeMax; + public int mPlaybackVolumeHandling; + public int mPlaybackStream; + public int mPlaybackState; + + public void resetPlaybackInfo() { + mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL; + mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; + mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; + mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; + mPlaybackStream = AudioManager.STREAM_MUSIC; + mPlaybackState = RemoteControlClient.PLAYSTATE_STOPPED; + } /** precondition: mediaIntent != null, eventReceiver != null */ public RemoteControlStackEntry(PendingIntent mediaIntent, ComponentName eventReceiver) { @@ -4136,6 +4259,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mReceiverComponent = eventReceiver; mCallingUid = -1; mRcClient = null; + mRccId = ++sLastRccId; + + resetPlaybackInfo(); } public void unlinkToRcClientDeath() { @@ -4185,9 +4311,44 @@ public class AudioService extends IAudioService.Stub implements OnFinished { pw.println(" pi: " + rcse.mMediaIntent + " -- ercvr: " + rcse.mReceiverComponent + " -- client: " + rcse.mRcClient + - " -- uid: " + rcse.mCallingUid); + " -- uid: " + rcse.mCallingUid + + " -- type: " + rcse.mPlaybackType + + " state: " + rcse.mPlaybackState); + } + } + } + + /** + * Helper function: + * Display in the log the current entries in the remote control stack, focusing + * on RemoteControlClient data + */ + private void dumpRCCStack(PrintWriter pw) { + pw.println("\nRemote Control Client stack entries:"); + synchronized(mRCStack) { + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + pw.println(" uid: " + rcse.mCallingUid + + " -- id: " + rcse.mRccId + + " -- type: " + rcse.mPlaybackType + + " -- state: " + rcse.mPlaybackState + + " -- vol handling: " + rcse.mPlaybackVolumeHandling + + " -- vol: " + rcse.mPlaybackVolume + + " -- volMax: " + rcse.mPlaybackVolumeMax); } } + synchronized (mMainRemote) { + pw.println("\nRemote Volume State:"); + pw.println(" has remote: " + mHasRemotePlayback); + pw.println(" is remote active: " + mMainRemoteIsActive); + pw.println(" rccId: " + mMainRemote.mRccId); + pw.println(" volume handling: " + + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ? + "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)")); + pw.println(" volume: " + mMainRemote.mVolume); + pw.println(" volume steps: " + mMainRemote.mVolumeMax); + } } /** @@ -4556,13 +4717,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) + * @return the unique ID of the RemoteControlStackEntry associated with the RemoteControlClient * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient * without modifying the RC stack, but while still causing the display to refresh (will * become blank as a result of this) */ - public void registerRemoteControlClient(PendingIntent mediaIntent, + public int registerRemoteControlClient(PendingIntent mediaIntent, IRemoteControlClient rcClient, String callingPackageName) { if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient); + int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; synchronized(mAudioFocusLock) { synchronized(mRCStack) { // store the new display information @@ -4581,8 +4744,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { rcse.mCallingUid = Binder.getCallingUid(); if (rcClient == null) { // here rcse.mRcClientDeathHandler is null; + rcse.resetPlaybackInfo(); break; } + rccId = rcse.mRccId; // there is a new (non-null) client: // 1/ give the new client the current display (if any) @@ -4616,6 +4781,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } } + return rccId; } /** @@ -4790,6 +4956,248 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + // FIXME send a message instead of updating the stack synchronously + public void setPlaybackInfoForRcc(int rccId, int what, int value) { + if(DEBUG_RC) Log.d(TAG, "setPlaybackInfoForRcc(id="+rccId+", what="+what+",val="+value+")"); + synchronized(mRCStack) { + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if (rcse.mRccId == rccId) { + switch (what) { + case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE: + rcse.mPlaybackType = value; + postReevaluateRemote(); + break; + case RemoteControlClient.PLAYBACKINFO_VOLUME: + rcse.mPlaybackVolume = value; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemote.mVolume = value; + mVolumePanel.postHasNewRemotePlaybackInfo(); + } + } + break; + case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX: + rcse.mPlaybackVolumeMax = value; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemote.mVolumeMax = value; + mVolumePanel.postHasNewRemotePlaybackInfo(); + } + } + break; + case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING: + rcse.mPlaybackVolumeHandling = value; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemote.mVolumeHandling = value; + mVolumePanel.postHasNewRemotePlaybackInfo(); + } + } + break; + case RemoteControlClient.PLAYBACKINFO_USES_STREAM: + rcse.mPlaybackStream = value; + break; + case RemoteControlClient.PLAYBACKINFO_PLAYSTATE: + rcse.mPlaybackState = value; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemoteIsActive = isPlaystateActive(value); + postReevaluateRemote(); + } + } + break; + default: + Log.e(TAG, "unhandled key " + what + " for RCC " + rccId); + break; + } + return; + } + } + } + } + + /** + * Checks if a remote client is active on the supplied stream type. Update the remote stream + * volume state if found and playing + * @param streamType + * @return false if no remote playing is currently playing + */ + private boolean checkUpdateRemoteStateIfActive(int streamType) { + synchronized(mRCStack) { + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) + && isPlaystateActive(rcse.mPlaybackState) + && (rcse.mPlaybackStream == streamType)) { + if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType + + ", vol =" + rcse.mPlaybackVolume); + synchronized (mMainRemote) { + mMainRemote.mRccId = rcse.mRccId; + mMainRemote.mVolume = rcse.mPlaybackVolume; + mMainRemote.mVolumeMax = rcse.mPlaybackVolumeMax; + mMainRemote.mVolumeHandling = rcse.mPlaybackVolumeHandling; + mMainRemoteIsActive = true; + } + return true; + } + } + } + synchronized (mMainRemote) { + mMainRemoteIsActive = false; + } + return false; + } + + /** + * Returns true if the given playback state is considered "active", i.e. it describes a state + * where playback is happening, or about to + * @param playState the playback state to evaluate + * @return true if active, false otherwise (inactive or unknown) + */ + private static boolean isPlaystateActive(int playState) { + switch (playState) { + case RemoteControlClient.PLAYSTATE_PLAYING: + case RemoteControlClient.PLAYSTATE_BUFFERING: + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + case RemoteControlClient.PLAYSTATE_REWINDING: + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + return true; + default: + return false; + } + } + + private 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 + mVolumePanel.postRemoteVolumeChanged(streamType, flags); + } + + private void sendVolumeUpdateToRemote(int rccId, int direction) { + if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); } + if (direction == 0) { + // only handling discrete events + return; + } + String packageForRcc = null; + synchronized (mRCStack) { + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? + if (rcse.mRccId == rccId) { + packageForRcc = rcse.mReceiverComponent.getPackageName(); + break; + } + } + } + if (packageForRcc != null) { + Intent intent = new Intent(Intent.ACTION_VOLUME_UPDATE); + intent.putExtra(Intent.EXTRA_VOLUME_UPDATE_DIRECTION, direction); + intent.setPackage(packageForRcc); + mContext.sendBroadcast(intent); + } + } + + public int getRemoteStreamMaxVolume() { + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return 0; + } + return mMainRemote.mVolumeMax; + } + } + + public int getRemoteStreamVolume() { + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return 0; + } + return mMainRemote.mVolume; + } + } + + public void setRemoteStreamVolume(int vol) { + if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); } + int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return; + } + rccId = mMainRemote.mRccId; + } + String packageForRcc = null; + synchronized (mRCStack) { + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if (rcse.mRccId == rccId) { + //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? + packageForRcc = rcse.mReceiverComponent.getPackageName(); + break; + } + } + } + if (packageForRcc != null) { + Intent intent = new Intent(Intent.ACTION_VOLUME_UPDATE); + intent.putExtra(Intent.EXTRA_VOLUME_UPDATE_VALUE, vol); + intent.setPackage(packageForRcc); + mContext.sendBroadcast(intent); + } + } + + /** + * Call to make AudioService reevaluate whether it's in a mode where remote players should + * have their volume controlled. In this implementation this is only to reset whether + * VolumePanel should display remote volumes + */ + private void postReevaluateRemote() { + sendMsg(mAudioHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0); + } + + private void onReevaluateRemote() { + if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); } + // is there a registered RemoteControlClient that is handling remote playback + boolean hasRemotePlayback = false; + synchronized (mRCStack) { + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if (rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) { + hasRemotePlayback = true; + break; + } + } + } + synchronized (mMainRemote) { + if (mHasRemotePlayback != hasRemotePlayback) { + mHasRemotePlayback = hasRemotePlayback; + mVolumePanel.postRemoteSliderVisibility(hasRemotePlayback); + } + } + } + //========================================================================================== // Device orientation //========================================================================================== @@ -4871,9 +5279,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); - // TODO probably a lot more to do here than just the audio focus and remote control stacks dumpFocusStack(pw); dumpRCStack(pw); + dumpRCCStack(pw); dumpStreamStates(pw); pw.println("\nAudio routes:"); pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mMainType)); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index fc5b8f1..83483c6 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -35,6 +35,8 @@ interface IAudioService { void adjustVolume(int direction, int flags); + oneway void adjustLocalOrRemoteStreamVolume(int streamType, int direction); + void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags); void adjustStreamVolume(int streamType, int direction, int flags); @@ -43,6 +45,8 @@ interface IAudioService { void setStreamVolume(int streamType, int index, int flags); + oneway void setRemoteStreamVolume(int index); + void setMasterVolume(int index, int flags); void setStreamSolo(int streamType, boolean state, IBinder cb); @@ -119,7 +123,7 @@ interface IAudioService { oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c); oneway void unregisterMediaButtonEventReceiverForCalls(); - oneway void registerRemoteControlClient(in PendingIntent mediaIntent, + int registerRemoteControlClient(in PendingIntent mediaIntent, in IRemoteControlClient rcClient, in String callingPackageName); oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent, in IRemoteControlClient rcClient); @@ -128,6 +132,10 @@ interface IAudioService { oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd); oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h); + oneway void setPlaybackInfoForRcc(int rccId, int what, int value); + int getRemoteStreamMaxVolume(); + int getRemoteStreamVolume(); + void startBluetoothSco(IBinder cb); void stopBluetoothSco(IBinder cb); diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index f1c4d34..5b8035e 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -18,6 +18,7 @@ package android.media; import android.app.PendingIntent; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -26,9 +27,11 @@ import android.graphics.RectF; import android.media.MediaMetadataRetriever; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.util.Log; @@ -131,6 +134,88 @@ public class RemoteControlClient public final static int PLAYSTATE_NONE = 0; /** + * @hide (to be un-hidden) + * The default playback type, "local", indicating the presentation of the media is happening on + * the same device (e.g. a phone, a tablet) as where it is controlled from. + */ + public final static int PLAYBACK_TYPE_LOCAL = 0; + /** + * @hide (to be un-hidden) + * A playback type indicating the presentation of the media is happening on + * a different device (i.e. the remote device) than where it is controlled from. + */ + public final static int PLAYBACK_TYPE_REMOTE = 1; + private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL; + private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE; + /** + * @hide (to be un-hidden) + * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled + * from this object. An example of fixed playback volume is a remote player, playing over HDMI + * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the + * source. + * @see #PLAYBACKINFO_VOLUME_HANDLING. + */ + public final static int PLAYBACK_VOLUME_FIXED = 0; + /** + * @hide (to be un-hidden) + * Playback information indicating the playback volume is variable and can be controlled from + * this object. + * @see #PLAYBACKINFO_VOLUME_HANDLING. + */ + public final static int PLAYBACK_VOLUME_VARIABLE = 1; + /** + * @hide (to be un-hidden) + * The playback information value indicating the value of a given information type is invalid. + * @see #PLAYBACKINFO_VOLUME_HANDLING. + */ + public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE; + + //========================================== + // Public keys for playback information + /** + * @hide (to be un-hidden) + * Playback information that defines the type of playback associated with this + * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}. + */ + public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1; + /** + * @hide (to be un-hidden) + * Playback information that defines at what volume the playback associated with this + * RemoteControlClient is performed. This information is only used when the playback type is not + * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}). + */ + public final static int PLAYBACKINFO_VOLUME = 2; + /** + * @hide (to be un-hidden) + * Playback information that defines the maximum volume volume value that is supported + * by the playback associated with this RemoteControlClient. This information is only used + * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}). + */ + public final static int PLAYBACKINFO_VOLUME_MAX = 3; + /** + * @hide (to be un-hidden) + * Playback information that defines how volume is handled for the presentation of the media. + * @see #PLAYBACK_VOLUME_FIXED + * @see #PLAYBACK_VOLUME_VARIABLE + */ + public final static int PLAYBACKINFO_VOLUME_HANDLING = 4; + /** + * @hide (to be un-hidden) + * Playback information that defines over what stream type the media is presented. + */ + public final static int PLAYBACKINFO_USES_STREAM = 5; + + //========================================== + // Private keys for playback information + /** + * @hide + * Used internally to relay playback state (set by the application with + * {@link #setPlaybackState(int)}) to AudioService + */ + public final static int PLAYBACKINFO_PLAYSTATE = 255; + + + /** * Flag indicating a RemoteControlClient makes use of the "previous" media key. * * @see #setTransportControlFlags(int) @@ -516,6 +601,8 @@ public class RemoteControlClient // send to remote control display if conditions are met sendPlaybackState_syncCacheLock(); + // update AudioService + sendAudioServiceNewPlaybackInfo_syncCacheLock(PLAYBACKINFO_PLAYSTATE, state); } } } @@ -542,6 +629,122 @@ public class RemoteControlClient } } + /** @hide */ + public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE; + /** @hide */ + // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC] + public final static int DEFAULT_PLAYBACK_VOLUME = 15; + + private int mPlaybackType = PLAYBACK_TYPE_LOCAL; + private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME; + private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME; + private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING; + private int mPlaybackStream = AudioManager.STREAM_MUSIC; + + /** + * @hide (to be un-hidden) + * Set information describing information related to the playback of media so the system + * can implement additional behavior to handle non-local playback usecases. + * @param what a key to specify the type of information to set. Valid keys are + * {@link #PLAYBACKINFO_PLAYBACK_TYPE}, + * {@link #PLAYBACKINFO_USES_STREAM}, + * {@link #PLAYBACKINFO_VOLUME}, + * {@link #PLAYBACKINFO_VOLUME_MAX}, + * and {@link #PLAYBACKINFO_VOLUME_HANDLING}. + * @param value the value for the supplied information to set. + */ + public void setPlaybackInformation(int what, int value) { + synchronized(mCacheLock) { + switch (what) { + case PLAYBACKINFO_PLAYBACK_TYPE: + if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) { + if (mPlaybackType != value) { + mPlaybackType = value; + sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); + } + } else { + Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE"); + } + break; + case PLAYBACKINFO_VOLUME: + if ((value > -1) && (value <= mPlaybackVolumeMax)) { + if (mPlaybackVolume != value) { + mPlaybackVolume = value; + sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); + } + } else { + Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME"); + } + break; + case PLAYBACKINFO_VOLUME_MAX: + if (value > 0) { + if (mPlaybackVolumeMax != value) { + mPlaybackVolumeMax = value; + sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); + } + } else { + Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX"); + } + break; + case PLAYBACKINFO_USES_STREAM: + if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) { + mPlaybackStream = value; + } else { + Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM"); + } + break; + case PLAYBACKINFO_VOLUME_HANDLING: + if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) { + if (mPlaybackVolumeHandling != value) { + mPlaybackVolumeHandling = value; + sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); + } + } else { + Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING"); + } + break; + default: + // not throwing an exception or returning an error if more keys are to be + // supported in the future + Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what); + break; + } + } + } + + /** + * @hide (to be un-hidden) + * Return playback information represented as an integer value. + * @param what a key to specify the type of information to retrieve. Valid keys are + * {@link #PLAYBACKINFO_PLAYBACK_TYPE}, + * {@link #PLAYBACKINFO_USES_STREAM}, + * {@link #PLAYBACKINFO_VOLUME}, + * {@link #PLAYBACKINFO_VOLUME_MAX}, + * and {@link #PLAYBACKINFO_VOLUME_HANDLING}. + * @return the current value for the given information type, or + * {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or + * the value is unknown. + */ + public int getIntPlaybackInformation(int what) { + synchronized(mCacheLock) { + switch (what) { + case PLAYBACKINFO_PLAYBACK_TYPE: + return mPlaybackType; + case PLAYBACKINFO_VOLUME: + return mPlaybackVolume; + case PLAYBACKINFO_VOLUME_MAX: + return mPlaybackVolumeMax; + case PLAYBACKINFO_USES_STREAM: + return mPlaybackStream; + case PLAYBACKINFO_VOLUME_HANDLING: + return mPlaybackVolumeHandling; + default: + Log.e(TAG, "getIntPlaybackInformation() unknown key " + what); + return PLAYBACKINFO_INVALID_VALUE; + } + } + } + /** * Lock for all cached data */ @@ -675,6 +878,27 @@ public class RemoteControlClient } }; + /** + * @hide + * Default value for the unique identifier + */ + public final static int RCSE_ID_UNREGISTERED = -1; + /** + * Unique identifier of the RemoteControlStackEntry in AudioService with which + * this RemoteControlClient is associated. + */ + private int mRcseId = RCSE_ID_UNREGISTERED; + /** + * @hide + * To be only used by AudioManager after it has received the unique id from + * IAudioService.registerRemoteControlClient() + * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which + * this RemoteControlClient is associated. + */ + public void setRcseId(int id) { + mRcseId = id; + } + private EventHandler mEventHandler; private final static int MSG_REQUEST_PLAYBACK_STATE = 1; private final static int MSG_REQUEST_METADATA = 2; @@ -731,6 +955,9 @@ public class RemoteControlClient } } + //=========================================================== + // Communication with IRemoteControlDisplay + private void detachFromDisplay_syncCacheLock() { mRcDisplay = null; mArtworkExpectedWidth = ARTWORK_INVALID_SIZE; @@ -802,6 +1029,37 @@ public class RemoteControlClient } } + //=========================================================== + // Communication with AudioService + + private static IAudioService sService; + + private static IAudioService getService() + { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); + sService = IAudioService.Stub.asInterface(b); + return sService; + } + + private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) { + if (mRcseId == RCSE_ID_UNREGISTERED) { + return; + } + Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value); + IAudioService service = getService(); + try { + service.setPlaybackInfoForRcc(mRcseId, what, value); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in sendAudioServiceNewPlaybackInfo_syncCacheLock", e); + } + } + + //=========================================================== + // Message handlers + private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) { synchronized (mCacheLock) { // this remote control client is told it is the "focused" one: @@ -836,6 +1094,9 @@ public class RemoteControlClient } } + //=========================================================== + // Internal utilities + /** * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap. * If the bitmap fits, then do nothing and return the original. diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java index 5aa764b..29a5573 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java +++ b/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java @@ -213,16 +213,13 @@ public abstract class KeyguardViewBase extends FrameLayout { Context.AUDIO_SERVICE); } } - // Volume buttons should only function for music. - if (mAudioManager.isMusicActive()) { - // TODO: Actually handle MUTE. - mAudioManager.adjustStreamVolume( - AudioManager.STREAM_MUSIC, - keyCode == KeyEvent.KEYCODE_VOLUME_UP - ? AudioManager.ADJUST_RAISE - : AudioManager.ADJUST_LOWER, - 0); - } + // Volume buttons should only function for music (local or remote). + // TODO: Actually handle MUTE. + mAudioManager.adjustLocalOrRemoteStreamVolume( + AudioManager.STREAM_MUSIC, + keyCode == KeyEvent.KEYCODE_VOLUME_UP + ? AudioManager.ADJUST_RAISE + : AudioManager.ADJUST_LOWER); // Don't execute default volume behavior return true; } else { |