AudioManager: Add support for master mute
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index 193460a..9152cc3 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -91,6 +91,7 @@
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 @@
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 @@
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);
}
@@ -452,6 +451,19 @@
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
@@ -485,6 +497,18 @@
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)
@@ -683,6 +707,11 @@
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 edb2d87..3f5ca69 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -372,11 +372,12 @@
/**
* @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
@@ -397,7 +398,8 @@
/**
* @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:
@@ -426,7 +428,13 @@
}
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
- // TODO: Actually handle MUTE.
+ if (event.getRepeatCount() == 0) {
+ if (mUseMasterVolume) {
+ setMasterMute(!isMasterMute());
+ } else {
+ // TODO: Actually handle MUTE.
+ }
+ }
break;
}
}
@@ -434,7 +442,8 @@
/**
* @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:
@@ -460,9 +469,6 @@
mVolumeKeyUpTime = SystemClock.uptimeMillis();
break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- // TODO: Actually handle MUTE.
- break;
}
}
@@ -657,7 +663,7 @@
IAudioService service = getService();
try {
if (mUseMasterVolume) {
- return service.getMasterVolume();
+ return service.getLastAudibleMasterVolume();
} else {
return service.getLastAudibleStreamVolume(streamType);
}
@@ -784,6 +790,35 @@
}
/**
+ * 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
* logic.
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 73ca607..b858320 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -146,6 +146,7 @@
private AudioHandler mAudioHandler;
/** @see VolumeStreamState */
private VolumeStreamState[] mStreamStates;
+ private MasterMuteState mMasterMuteState;
private SettingsObserver mSettingsObserver;
private int mMode;
@@ -446,6 +447,7 @@
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++) {
@@ -762,6 +764,16 @@
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);
@@ -770,7 +782,8 @@
}
public int getMasterVolume() {
- return Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
+ if (isMasterMute()) return 0;
+ return getLastAudibleMasterVolume();
}
public void setMasterVolume(int volume, int flags) {
@@ -794,6 +807,11 @@
return (mStreamStates[streamType].getIndex(device, true /* lastAudible */) + 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() {
synchronized(mSettingsLock) {
@@ -2231,6 +2249,122 @@
}
}
+ public class MasterMuteState {
+
+ private ArrayList<MasterMuteDeathHandler> mDeathHandlers;
+
+ private MasterMuteState() {
+ mDeathHandlers = new ArrayList<MasterMuteDeathHandler>();
+ }
+
+ 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 6780c1d..17d8e4d 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -45,6 +45,10 @@
boolean isStreamMute(int streamType);
+ void setMasterMute(boolean state, IBinder cb);
+
+ boolean isMasterMute();
+
int getStreamVolume(int streamType);
int getMasterVolume();
@@ -55,6 +59,8 @@
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 abed18f..83f7788 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 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 @@
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 @@
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 f1fe43b..b87b8c3 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 @@
// 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 @@
// 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;
}