From cfbcd3bf21c8453e9afd7740fdb7fc272c362389 Mon Sep 17 00:00:00 2001 From: Mike Lockwood Date: Mon, 14 Nov 2011 10:47:42 -0800 Subject: [PATCH] AudioManager: Add support for master mute Change-Id: I98e87d1e266e0523c4aacb23cc5c4b3fdc7a1eac Signed-off-by: Mike Lockwood --- core/java/android/view/VolumePanel.java | 37 ++++- media/java/android/media/AudioManager.java | 51 +++++-- media/java/android/media/AudioService.java | 136 +++++++++++++++++- media/java/android/media/IAudioService.aidl | 6 + .../impl/PhoneFallbackEventHandler.java | 8 +- .../internal/policy/impl/PhoneWindow.java | 4 +- 6 files changed, 222 insertions(+), 20 deletions(-) diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java index c456ee4bef63d..1ea41d77a792e 100644 --- a/core/java/android/view/VolumePanel.java +++ b/core/java/android/view/VolumePanel.java @@ -91,6 +91,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private static final int MSG_VIBRATE = 4; private static final int MSG_TIMEOUT = 5; private static final int MSG_RINGER_MODE_CHANGED = 6; + private static final int MSG_MUTE_CHANGED = 7; // Pseudo stream type for master volume private static final int STREAM_MASTER = -100; @@ -295,8 +296,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private boolean isMuted(int streamType) { if (streamType == STREAM_MASTER) { - // master volume mute not yet supported - return false; + return mAudioService.isMasterMute(); } else { return mAudioService.isStreamMute(streamType); } @@ -328,8 +328,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private int getLastAudibleStreamVolume(int streamType) { if (streamType == STREAM_MASTER) { - // master volume mute not yet supported - return getStreamVolume(STREAM_MASTER); + return mAudioService.getLastAudibleMasterVolume(); } else { return mAudioService.getLastAudibleStreamVolume(streamType); } @@ -460,6 +459,19 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie postVolumeChanged(STREAM_MASTER, flags); } + public void postMuteChanged(int streamType, int flags) { + if (hasMessages(MSG_VOLUME_CHANGED)) return; + if (mStreamControls == null) { + createSliders(); + } + removeMessages(MSG_FREE_RESOURCES); + obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget(); + } + + public void postMasterMuteChanged(int flags) { + postMuteChanged(STREAM_MASTER, flags); + } + /** * Override this if you have other work to do when the volume changes (for * example, vibrating, playing a sound, etc.). Make sure to call through to @@ -493,6 +505,18 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie resetTimeout(); } + protected void onMuteChanged(int streamType, int flags) { + + if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")"); + + StreamControl sc = mStreamControls.get(streamType); + if (sc != null) { + sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes); + } + + onVolumeChanged(streamType, flags); + } + protected void onShowVolumeChanged(int streamType, int flags) { int index = isMuted(streamType) ? getLastAudibleStreamVolume(streamType) @@ -693,6 +717,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie break; } + case MSG_MUTE_CHANGED: { + onMuteChanged(msg.arg1, msg.arg2); + break; + } + case MSG_FREE_RESOURCES: { onFreeResources(); break; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 038cee400ef21..a4ec8b8ce094e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -378,11 +378,12 @@ public class AudioManager { /** * @hide */ - public void preDispatchKeyEvent(int keyCode, int stream) { + public void preDispatchKeyEvent(KeyEvent event, int stream) { /* * If the user hits another key within the play sound delay, then * cancel the sound */ + int keyCode = event.getKeyCode(); if (keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && mVolumeKeyUpTime + VolumePanel.PLAY_SOUND_DELAY @@ -403,7 +404,8 @@ public class AudioManager { /** * @hide */ - public void handleKeyDown(int keyCode, int stream) { + public void handleKeyDown(KeyEvent event, int stream) { + int keyCode = event.getKeyCode(); switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: @@ -432,7 +434,13 @@ public class AudioManager { } break; case KeyEvent.KEYCODE_VOLUME_MUTE: - // TODO: Actually handle MUTE. + if (event.getRepeatCount() == 0) { + if (mUseMasterVolume) { + setMasterMute(!isMasterMute()); + } else { + // TODO: Actually handle MUTE. + } + } break; } } @@ -440,7 +448,8 @@ public class AudioManager { /** * @hide */ - public void handleKeyUp(int keyCode, int stream) { + public void handleKeyUp(KeyEvent event, int stream) { + int keyCode = event.getKeyCode(); switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: @@ -466,9 +475,6 @@ public class AudioManager { mVolumeKeyUpTime = SystemClock.uptimeMillis(); break; - case KeyEvent.KEYCODE_VOLUME_MUTE: - // TODO: Actually handle MUTE. - break; } } @@ -663,7 +669,7 @@ public class AudioManager { IAudioService service = getService(); try { if (mUseMasterVolume) { - return service.getMasterVolume(); + return service.getLastAudibleMasterVolume(); } else { return service.getLastAudibleStreamVolume(streamType); } @@ -789,6 +795,35 @@ public class AudioManager { } } + /** + * set master mute state. + * + * @hide + */ + public void setMasterMute(boolean state) { + IAudioService service = getService(); + try { + service.setMasterMute(state, mICallBack); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in setMasterMute", e); + } + } + + /** + * get master mute state. + * + * @hide + */ + public boolean isMasterMute() { + IAudioService service = getService(); + try { + return service.isMasterMute(); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in isMasterMute", e); + return false; + } + } + /** * forces the stream controlled by hard volume keys * specifying streamType == -1 releases control to the diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 90376ae6d4b25..e3b515514dce6 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -141,6 +141,7 @@ public class AudioService extends IAudioService.Stub { private AudioHandler mAudioHandler; /** @see VolumeStreamState */ private VolumeStreamState[] mStreamStates; + private MasterMuteState mMasterMuteState; private SettingsObserver mSettingsObserver; private int mMode; @@ -438,6 +439,7 @@ public class AudioService extends IAudioService.Stub { for (int i = 0; i < numStreamTypes; i++) { streams[i] = new VolumeStreamState(System.VOLUME_SETTINGS[STREAM_VOLUME_ALIAS[i]], i); } + mMasterMuteState = new MasterMuteState(); // Correct stream index values for streams with aliases for (int i = 0; i < numStreamTypes; i++) { @@ -714,6 +716,16 @@ public class AudioService extends IAudioService.Stub { return (mStreamStates[streamType].muteCount() != 0); } + /** @see AudioManager#setMasterMute(boolean, IBinder) */ + public void setMasterMute(boolean state, IBinder cb) { + mMasterMuteState.mute(cb, state); + } + + /** get master mute state. */ + public boolean isMasterMute() { + return (mMasterMuteState.muteCount() != 0); + } + /** @see AudioManager#getStreamVolume(int) */ public int getStreamVolume(int streamType) { ensureValidStreamType(streamType); @@ -721,7 +733,8 @@ public class AudioService extends IAudioService.Stub { } public int getMasterVolume() { - return Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME); + if (isMasterMute()) return 0; + return getLastAudibleMasterVolume(); } public void setMasterVolume(int volume, int flags) { @@ -744,6 +757,11 @@ public class AudioService extends IAudioService.Stub { return (mStreamStates[streamType].mLastAudibleIndex + 5) / 10; } + /** Get last audible master volume before it was muted. */ + public int getLastAudibleMasterVolume() { + return Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME); + } + /** @see AudioManager#getRingerMode() */ public int getRingerMode() { return mRingerMode; @@ -2071,6 +2089,122 @@ public class AudioService extends IAudioService.Stub { } } + public class MasterMuteState { + + private ArrayList mDeathHandlers; + + private MasterMuteState() { + mDeathHandlers = new ArrayList(); + } + + public void mute(IBinder cb, boolean state) { + MasterMuteDeathHandler handler = getDeathHandler(cb, state); + if (handler == null) { + Log.e(TAG, "Could not get client death handler for master volume"); + return; + } + handler.mute(state); + } + + private class MasterMuteDeathHandler implements IBinder.DeathRecipient { + private IBinder mICallback; // To be notified of client's death + private int mMuteCount; // Number of active mutes for this client + + MasterMuteDeathHandler(IBinder cb) { + mICallback = cb; + } + + public void mute(boolean state) { + synchronized(mDeathHandlers) { + if (state) { + if (mMuteCount == 0) { + // Register for client death notification + try { + // mICallback can be 0 if muted by AudioService + if (mICallback != null) { + mICallback.linkToDeath(this, 0); + } + mDeathHandlers.add(this); + // If the stream is not yet muted by any client, set lvel to 0 + if (muteCount() == 0) { + AudioSystem.setMasterMute(true); + mVolumePanel.postMasterMuteChanged(AudioManager.FLAG_SHOW_UI); + } + } catch (RemoteException e) { + // Client has died! + binderDied(); + mDeathHandlers.notify(); + return; + } + } else { + Log.w(TAG, "master volume was already muted by this client"); + } + mMuteCount++; + } else { + if (mMuteCount == 0) { + Log.e(TAG, "unexpected unmute for master volume"); + } else { + mMuteCount--; + if (mMuteCount == 0) { + // Unregistr from client death notification + mDeathHandlers.remove(this); + // mICallback can be 0 if muted by AudioService + if (mICallback != null) { + mICallback.unlinkToDeath(this, 0); + } + if (muteCount() == 0) { + AudioSystem.setMasterMute(false); + mVolumePanel.postMasterMuteChanged(AudioManager.FLAG_SHOW_UI); + } + } + } + } + mDeathHandlers.notify(); + } + } + + public void binderDied() { + Log.w(TAG, "Volume service client died for master volume"); + if (mMuteCount != 0) { + // Reset all active mute requests from this client. + mMuteCount = 1; + mute(false); + } + } + } + + private int muteCount() { + int count = 0; + int size = mDeathHandlers.size(); + for (int i = 0; i < size; i++) { + count += mDeathHandlers.get(i).mMuteCount; + } + return count; + } + + private MasterMuteDeathHandler getDeathHandler(IBinder cb, boolean state) { + synchronized(mDeathHandlers) { + MasterMuteDeathHandler handler; + int size = mDeathHandlers.size(); + for (int i = 0; i < size; i++) { + handler = mDeathHandlers.get(i); + if (cb == handler.mICallback) { + return handler; + } + } + // If this is the first mute request for this client, create a new + // client death handler. Otherwise, it is an out of sequence unmute request. + if (state) { + handler = new MasterMuteDeathHandler(cb); + } else { + Log.w(TAG, "stream was not muted by this client"); + handler = null; + } + return handler; + } + } + } + /** Thread that handles native AudioSystem control. */ private class AudioSystemThread extends Thread { AudioSystemThread() { diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 6780c1da3fdca..17d8e4df481f0 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -45,6 +45,10 @@ interface IAudioService { boolean isStreamMute(int streamType); + void setMasterMute(boolean state, IBinder cb); + + boolean isMasterMute(); + int getStreamVolume(int streamType); int getMasterVolume(); @@ -55,6 +59,8 @@ interface IAudioService { int getLastAudibleStreamVolume(int streamType); + int getLastAudibleMasterVolume(); + void setRingerMode(int ringerMode); int getRingerMode(); diff --git a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java index abed18fec0a7d..83f77880836dc 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java +++ b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java @@ -52,8 +52,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { } public void preDispatchKeyEvent(KeyEvent event) { - getAudioManager().preDispatchKeyEvent(event.getKeyCode(), - AudioManager.USE_DEFAULT_STREAM_TYPE); + getAudioManager().preDispatchKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE); } public boolean dispatchKeyEvent(KeyEvent event) { @@ -79,7 +78,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: { - getAudioManager().handleKeyDown(keyCode, AudioManager.USE_DEFAULT_STREAM_TYPE); + getAudioManager().handleKeyDown(event, AudioManager.USE_DEFAULT_STREAM_TYPE); return true; } @@ -197,8 +196,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { AudioManager audioManager = (AudioManager)mContext.getSystemService( Context.AUDIO_SERVICE); if (audioManager != null) { - getAudioManager().handleKeyUp(keyCode, - AudioManager.USE_DEFAULT_STREAM_TYPE); + getAudioManager().handleKeyUp(event, AudioManager.USE_DEFAULT_STREAM_TYPE); } } return true; diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index af86ae9547946..94d8ba3b46cc8 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -1416,7 +1416,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // doesn't have one of these. In this case, we execute it here and // eat the event instead, because we have mVolumeControlStreamType // and they don't. - getAudioManager().handleKeyDown(keyCode, mVolumeControlStreamType); + getAudioManager().handleKeyDown(event, mVolumeControlStreamType); return true; } @@ -1478,7 +1478,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // doesn't have one of these. In this case, we execute it here and // eat the event instead, because we have mVolumeControlStreamType // and they don't. - getAudioManager().handleKeyUp(keyCode, mVolumeControlStreamType); + getAudioManager().handleKeyUp(event, mVolumeControlStreamType); return true; }