Route mute key events through MediaSessionService

This sends mute keys to the MediaSessionService and handles them
by toggling the appropriate stream. Muting remote playback is still
not supported.

bug:17501993
Change-Id: I18c5b037cde2175acbb64b118dd708514acfd8c9
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 543836b..ee9044e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -138,6 +138,17 @@
     public static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
 
     /**
+     * @hide Broadcast intent when a stream mute state changes.
+     * Includes the stream that changed and the new mute state
+     *
+     * @see #EXTRA_VOLUME_STREAM_TYPE
+     * @see #EXTRA_STREAM_VOLUME_MUTED
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String STREAM_MUTE_CHANGED_ACTION =
+        "android.media.STREAM_MUTE_CHANGED_ACTION";
+
+    /**
      * @hide Broadcast intent when the master volume changes.
      * Includes the new volume
      *
@@ -221,6 +232,13 @@
         "android.media.EXTRA_MASTER_VOLUME_MUTED";
 
     /**
+     * @hide The new stream volume mute state for the stream mute changed intent.
+     * Value is boolean
+     */
+    public static final String EXTRA_STREAM_VOLUME_MUTED =
+        "android.media.EXTRA_STREAM_VOLUME_MUTED";
+
+    /**
      * Broadcast Action: Wired Headset plugged in or unplugged.
      *
      * You <em>cannot</em> receive this through components declared
@@ -728,11 +746,7 @@
                 break;
             case KeyEvent.KEYCODE_VOLUME_MUTE:
                 if (event.getRepeatCount() == 0) {
-                    if (mUseMasterVolume) {
-                        setMasterMute(!isMasterMute());
-                    } else {
-                        // TODO: Actually handle MUTE.
-                    }
+                    MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
                 }
                 break;
         }
@@ -763,6 +777,9 @@
                 }
                 mVolumeKeyUpTime = SystemClock.uptimeMillis();
                 break;
+            case KeyEvent.KEYCODE_VOLUME_MUTE:
+                MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+                break;
         }
     }
 
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index d9586bc..616bdd1 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -15,6 +15,8 @@
  */
 package android.media;
 
+import android.os.IBinder;
+
 import com.android.server.LocalServices;
 
 /**
@@ -39,6 +41,9 @@
     public abstract void adjustMasterVolumeForUid(int steps, int flags, String callingPackage,
             int uid);
 
+    public abstract void setMasterMuteForUid(boolean state, int flags, String callingPackage,
+            IBinder cb, int uid);
+
     public abstract void setRingerModeDelegate(RingerModeDelegate delegate);
 
     public abstract int getRingerModeInternal();
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 9a3ec42..f1d6a0a 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -1517,12 +1517,20 @@
         if (mUseFixedVolume) {
             return;
         }
+        if (streamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+            streamType = getActiveStreamType(streamType);
+        }
 
         if (isStreamAffectedByMute(streamType)) {
             if (streamType == AudioSystem.STREAM_MUSIC) {
                 setSystemAudioMute(state);
             }
             mStreamStates[streamType].mute(cb, state);
+
+            Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+            intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
+            intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state);
+            sendBroadcastToAll(intent);
         }
     }
 
@@ -1544,6 +1552,9 @@
 
     /** get stream mute state. */
     public boolean isStreamMute(int streamType) {
+        if (streamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+            streamType = getActiveStreamType(streamType);
+        }
         synchronized (VolumeStreamState.class) {
             return mStreamStates[streamType].isMuted_syncVSS();
         }
@@ -1651,11 +1662,16 @@
 
     /** @see AudioManager#setMasterMute(boolean, int) */
     public void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb) {
+        setMasterMuteInternal(state, flags, callingPackage, cb, Binder.getCallingUid());
+    }
+
+    private void setMasterMuteInternal(boolean state, int flags, String callingPackage, IBinder cb,
+            int uid) {
         if (mUseFixedVolume) {
             return;
         }
-        if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, Binder.getCallingUid(),
-                callingPackage) != AppOpsManager.MODE_ALLOWED) {
+        if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
+                != AppOpsManager.MODE_ALLOWED) {
             return;
         }
         if (state != AudioSystem.getMasterMute()) {
@@ -1665,6 +1681,10 @@
             sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, state ? 1
                     : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY);
             sendMasterMuteUpdate(state, flags);
+
+            Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION);
+            intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, state);
+            sendBroadcastToAll(intent);
         }
     }
 
@@ -5781,6 +5801,12 @@
         public void setRingerModeInternal(int ringerMode, String caller) {
             AudioService.this.setRingerModeInternal(ringerMode, caller);
         }
+
+        @Override
+        public void setMasterMuteForUid(boolean state, int flags, String callingPackage, IBinder cb,
+                int uid) {
+            setMasterMuteInternal(state, flags, callingPackage, cb, uid);
+        }
     }
 
     //==========================================================================================
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index b37ee6e..4b9a929 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -190,6 +190,7 @@
         boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
         boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
         int direction = 0;
+        boolean isMute = false;
         switch (keyEvent.getKeyCode()) {
             case KeyEvent.KEYCODE_VOLUME_UP:
                 direction = AudioManager.ADJUST_RAISE;
@@ -198,15 +199,11 @@
                 direction = AudioManager.ADJUST_LOWER;
                 break;
             case KeyEvent.KEYCODE_VOLUME_MUTE:
-                // TODO
+                isMute = true;
                 break;
         }
-        if ((down || up) && direction != 0) {
+        if (down || up) {
             int flags;
-            // If this is action up we want to send a beep for non-music events
-            if (up) {
-                direction = 0;
-            }
             if (musicOnly) {
                 // This flag is used when the screen is off to only affect
                 // active media
@@ -219,9 +216,23 @@
                     flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
                 }
             }
-
-            mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
-                    direction, flags);
+            if (direction != 0) {
+                // If this is action up we want to send a beep for non-music events
+                if (up) {
+                    direction = 0;
+                }
+                mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
+                        direction, flags);
+            } else if (isMute) {
+                if (down) {
+                    // We need to send two volume events on down, one to mute
+                    // and one to show the UI
+                    mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
+                            MediaSessionManager.DIRECTION_MUTE, flags);
+                }
+                mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
+                        0 /* direction, causes UI to show on down */, flags);
+            }
         }
     }
 
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index b4fff8f..a4ef851 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -59,6 +59,14 @@
     private Context mContext;
 
     /**
+     * Special flag for sending the mute key to dispatchAdjustVolume used by the
+     * system.
+     *
+     * @hide
+     */
+    public static final int DIRECTION_MUTE = -99;
+
+    /**
      * @hide
      */
     public MediaSessionManager(Context context) {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 9440697..2c61d2c 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -35,6 +35,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
 import android.media.session.ParcelableVolumeInfo;
 import android.media.session.PlaybackState;
 import android.media.AudioAttributes;
@@ -92,6 +93,7 @@
     private final MediaSessionService mService;
     private final boolean mUseMasterVolume;
 
+    private final IBinder mICallback = new Binder();
     private final Object mLock = new Object();
     private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
             new ArrayList<ISessionControllerCallback>();
@@ -245,6 +247,7 @@
         if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
+        boolean isMute = direction == MediaSessionManager.DIRECTION_MUTE;
         if (direction > 1) {
             direction = 1;
         } else if (direction < -1) {
@@ -254,29 +257,52 @@
             if (mUseMasterVolume) {
                 // If this device only uses master volume and playback is local
                 // just adjust the master volume and return.
-                mAudioManagerInternal.adjustMasterVolumeForUid(direction, flags, packageName, uid);
+                if (isMute) {
+                    mAudioManagerInternal.setMasterMuteForUid(!mAudioManager.isMasterMute(),
+                            flags, packageName, mICallback, uid);
+                } else {
+                    mAudioManagerInternal.adjustMasterVolumeForUid(direction, flags, packageName,
+                            uid);
+                }
                 return;
             }
             int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
             if (useSuggested) {
                 if (AudioSystem.isStreamActive(stream, 0)) {
-                    mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
-                            flags, packageName, uid);
+                    if (isMute) {
+                        mAudioManager.setStreamMute(stream, !mAudioManager.isStreamMute(stream));
+                    } else {
+                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
+                                flags, packageName, uid);
+                    }
                 } else {
                     flags |= previousFlagPlaySound;
-                    mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
-                            AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags, packageName,
-                            uid);
+                    if (isMute) {
+                        mAudioManager.setStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE,
+                                !mAudioManager.isStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE));
+                    } else {
+                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
+                                AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags, packageName,
+                                uid);
+                    }
                 }
             } else {
-                mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
-                        packageName, uid);
+                if (isMute) {
+                    mAudioManager.setStreamMute(stream, !mAudioManager.isStreamMute(stream));
+                } else {
+                    mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
+                            packageName, uid);
+                }
             }
         } else {
             if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
                 // Nothing to do, the volume cannot be changed
                 return;
             }
+            if (isMute) {
+                Log.w(TAG, "Muting remote playback is not supported");
+                return;
+            }
             mSessionCb.adjustVolume(direction);
 
             int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index f11a3f9..b4ec607 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -40,6 +40,7 @@
 import android.media.session.ISessionManager;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -588,6 +589,8 @@
                 "android.media.AudioService.WAKELOCK_ACQUIRED";
         private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
 
+        private final IBinder mICallback = new Binder();
+
         private boolean mVoiceButtonDown = false;
         private boolean mVoiceButtonHandled = false;
 
@@ -720,8 +723,7 @@
         }
 
         @Override
-        public void dispatchAdjustVolume(int suggestedStream, int delta, int flags)
-                throws RemoteException {
+        public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
@@ -828,11 +830,21 @@
                 }
                 try {
                     if (mUseMasterVolume) {
-                        mAudioService.adjustMasterVolume(direction, flags,
-                                getContext().getOpPackageName());
+                        if (direction == MediaSessionManager.DIRECTION_MUTE) {
+                            mAudioService.setMasterMute(!mAudioService.isMasterMute(), flags,
+                                    getContext().getOpPackageName(), mICallback);
+                        } else {
+                            mAudioService.adjustMasterVolume(direction, flags,
+                                    getContext().getOpPackageName());
+                        }
                     } else {
-                        mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
-                                getContext().getOpPackageName());
+                        if (direction == MediaSessionManager.DIRECTION_MUTE) {
+                            mAudioService.setStreamMute(suggestedStream,
+                                    !mAudioService.isStreamMute(suggestedStream), mICallback);
+                        } else {
+                            mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
+                                    flags, getContext().getOpPackageName());
+                        }
                     }
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error adjusting default volume.", e);
@@ -841,7 +853,7 @@
                 session.adjustVolume(direction, flags, getContext().getPackageName(),
                         UserHandle.myUserId(), true);
                 if (session.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE
-                        && mRvc != null) {
+                        && mRvc != null && direction != MediaSessionManager.DIRECTION_MUTE) {
                     try {
                         mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
                     } catch (Exception e) {