Merge "AudioService: implement TV system volume" into lmp-dev
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 8993af8..050c268 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -44,6 +44,7 @@
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiTvClient;
 import android.hardware.usb.UsbManager;
 import android.media.MediaPlayer.OnCompletionListener;
@@ -144,7 +145,23 @@
     private final Context mContext;
     private final ContentResolver mContentResolver;
     private final AppOpsManager mAppOps;
-    private final boolean mVoiceCapable;
+
+    // the platform has no specific capabilities
+    private static final int PLATFORM_DEFAULT = 0;
+    // the platform is voice call capable (a phone)
+    private static final int PLATFORM_VOICE = 1;
+    // the platform is a television or a set-top box
+    private static final int PLATFORM_TELEVISION = 2;
+    // the platform type affects volume and silent mode behavior
+    private final int mPlatformType;
+
+    private boolean isPlatformVoice() {
+        return mPlatformType == PLATFORM_VOICE;
+    }
+
+    private boolean isPlatformTelevision() {
+        return mPlatformType == PLATFORM_TELEVISION;
+    }
 
     /** The controller for the volume UI. */
     private final VolumeController mVolumeController = new VolumeController();
@@ -243,9 +260,10 @@
      * NOTE: do not create loops in aliases!
      * Some streams alias to different streams according to device category (phone or tablet) or
      * use case (in call vs off call...). See updateStreamVolumeAlias() for more details.
-     *  mStreamVolumeAlias contains the default aliases for a voice capable device (phone) and
-     *  STREAM_VOLUME_ALIAS_NON_VOICE for a non voice capable device (tablet).*/
-    private final int[] STREAM_VOLUME_ALIAS = new int[] {
+     *  mStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device
+     *  (phone), STREAM_VOLUME_ALIAS_TELEVISION for a television or set-top box and
+     *  STREAM_VOLUME_ALIAS_DEFAULT for other devices (e.g. tablets).*/
+    private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
         AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
         AudioSystem.STREAM_RING,            // STREAM_SYSTEM
         AudioSystem.STREAM_RING,            // STREAM_RING
@@ -257,7 +275,19 @@
         AudioSystem.STREAM_RING,            // STREAM_DTMF
         AudioSystem.STREAM_MUSIC            // STREAM_TTS
     };
-    private final int[] STREAM_VOLUME_ALIAS_NON_VOICE = new int[] {
+    private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
+        AudioSystem.STREAM_MUSIC,       // STREAM_VOICE_CALL
+        AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM
+        AudioSystem.STREAM_MUSIC,       // STREAM_RING
+        AudioSystem.STREAM_MUSIC,       // STREAM_MUSIC
+        AudioSystem.STREAM_MUSIC,       // STREAM_ALARM
+        AudioSystem.STREAM_MUSIC,       // STREAM_NOTIFICATION
+        AudioSystem.STREAM_MUSIC,       // STREAM_BLUETOOTH_SCO
+        AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM_ENFORCED
+        AudioSystem.STREAM_MUSIC,       // STREAM_DTMF
+        AudioSystem.STREAM_MUSIC        // STREAM_TTS
+    };
+    private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
         AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
         AudioSystem.STREAM_MUSIC,           // STREAM_SYSTEM
         AudioSystem.STREAM_RING,            // STREAM_RING
@@ -455,9 +485,12 @@
     public final static int STREAM_REMOTE_MUSIC = -200;
 
     // Devices for which the volume is fixed and VolumePanel slider should be disabled
-    final int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
+    int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
             AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
-            AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET;
+            AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET |
+            AudioSystem.DEVICE_OUT_HDMI_ARC |
+            AudioSystem.DEVICE_OUT_SPDIF |
+            AudioSystem.DEVICE_OUT_AUX_LINE;
 
     // TODO merge orientation and rotation
     private final boolean mMonitorOrientation;
@@ -491,8 +524,16 @@
         mContext = context;
         mContentResolver = context.getContentResolver();
         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
-        mVoiceCapable = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_voice_capable);
+
+        if (mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_voice_capable)) {
+            mPlatformType = PLATFORM_VOICE;
+        } else if (context.getPackageManager().hasSystemFeature(
+                                                            PackageManager.FEATURE_TELEVISION)) {
+            mPlatformType = PLATFORM_TELEVISION;
+        } else {
+            mPlatformType = PLATFORM_DEFAULT;
+        }
 
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
@@ -622,10 +663,15 @@
                                     BluetoothProfile.A2DP);
         }
 
-        HdmiControlManager hdmiManager =
+        mHdmiManager =
                 (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
-        // Null if device is not Tv.
-        mHdmiTvClient = hdmiManager.getTvClient();
+        if (mHdmiManager != null) {
+            synchronized (mHdmiManager) {
+                mHdmiTvClient = mHdmiManager.getTvClient();
+                mHdmiPlaybackClient = mHdmiManager.getPlaybackClient();
+                mHdmiCecSink = false;
+            }
+        }
 
         sendMsg(mAudioHandler,
                 MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED,
@@ -670,6 +716,14 @@
         }
     }
 
+    private void checkAllFixedVolumeDevices()
+    {
+        int numStreamTypes = AudioSystem.getNumStreamTypes();
+        for (int streamType = 0; streamType < numStreamTypes; streamType++) {
+            mStreamStates[streamType].checkFixedVolumeDevices();
+        }
+    }
+
     private void createStreamStates() {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
@@ -678,6 +732,7 @@
             streams[i] = new VolumeStreamState(System.VOLUME_SETTINGS[mStreamVolumeAlias[i]], i);
         }
 
+        checkAllFixedVolumeDevices();
         checkAllAliasStreamVolumes();
     }
 
@@ -702,19 +757,32 @@
 
     private void updateStreamVolumeAlias(boolean updateVolumes) {
         int dtmfStreamAlias;
-        if (mVoiceCapable) {
-            mStreamVolumeAlias = STREAM_VOLUME_ALIAS;
+
+        switch (mPlatformType) {
+        case PLATFORM_VOICE:
+            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE;
             dtmfStreamAlias = AudioSystem.STREAM_RING;
-        } else {
-            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NON_VOICE;
+            break;
+        case PLATFORM_TELEVISION:
+            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
+            dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
+            break;
+        default:
+            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
             dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
         }
-        if (isInCommunication()) {
-            dtmfStreamAlias = AudioSystem.STREAM_VOICE_CALL;
-            mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
+
+        if (isPlatformTelevision()) {
+            mRingerModeAffectedStreams = 0;
         } else {
-            mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
+            if (isInCommunication()) {
+                dtmfStreamAlias = AudioSystem.STREAM_VOICE_CALL;
+                mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
+            } else {
+                mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
+            }
         }
+
         mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
         if (updateVolumes) {
             mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias]);
@@ -768,7 +836,7 @@
         if (ringerMode != ringerModeFromSettings) {
             Settings.Global.putInt(cr, Settings.Global.MODE_RINGER, ringerMode);
         }
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || isPlatformTelevision()) {
             ringerMode = AudioManager.RINGER_MODE_NORMAL;
         }
         synchronized(mSettingsLock) {
@@ -998,15 +1066,30 @@
 
             // Check if volume update should be send to Hdmi system audio.
             int newIndex = mStreamStates[streamType].getIndex(device);
-            if (mHdmiTvClient != null &&
-                streamTypeAlias == AudioSystem.STREAM_MUSIC &&
-                (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
-                oldIndex != newIndex) {
-                int maxIndex = getStreamMaxVolume(streamType);
-                synchronized (mHdmiTvClient) {
-                    if (mHdmiSystemAudioSupported) {
-                        mHdmiTvClient.setSystemAudioVolume(
-                                (oldIndex + 5) / 10, (newIndex + 5) / 10, maxIndex);
+            if (mHdmiManager != null) {
+                synchronized (mHdmiManager) {
+                    if (mHdmiTvClient != null &&
+                        streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+                        (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
+                        oldIndex != newIndex) {
+                        int maxIndex = getStreamMaxVolume(streamType);
+                        synchronized (mHdmiTvClient) {
+                            if (mHdmiSystemAudioSupported) {
+                                mHdmiTvClient.setSystemAudioVolume(
+                                        (oldIndex + 5) / 10, (newIndex + 5) / 10, maxIndex);
+                            }
+                        }
+                    }
+                    // mHdmiCecSink true => mHdmiPlaybackClient != null
+                    if (mHdmiCecSink &&
+                            streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+                            oldIndex != newIndex) {
+                        synchronized (mHdmiPlaybackClient) {
+                            int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN :
+                                                               KeyEvent.KEYCODE_VOLUME_UP;
+                            mHdmiPlaybackClient.sendKeyEvent(keyCode, true);
+                            mHdmiPlaybackClient.sendKeyEvent(keyCode, false);
+                        }
                     }
                 }
             }
@@ -1110,15 +1193,19 @@
                 }
             }
 
-            if (mHdmiTvClient != null &&
-                streamTypeAlias == AudioSystem.STREAM_MUSIC &&
-                (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
-                oldIndex != index) {
-                int maxIndex = getStreamMaxVolume(streamType);
-                synchronized (mHdmiTvClient) {
-                    if (mHdmiSystemAudioSupported) {
-                        mHdmiTvClient.setSystemAudioVolume(
-                                (oldIndex + 5) / 10, (index + 5) / 10, maxIndex);
+            if (mHdmiManager != null) {
+                synchronized (mHdmiManager) {
+                    if (mHdmiTvClient != null &&
+                        streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+                        (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
+                        oldIndex != index) {
+                        int maxIndex = getStreamMaxVolume(streamType);
+                        synchronized (mHdmiTvClient) {
+                            if (mHdmiSystemAudioSupported) {
+                                mHdmiTvClient.setSystemAudioVolume(
+                                        (oldIndex + 5) / 10, (index + 5) / 10, maxIndex);
+                            }
+                        }
                     }
                 }
             }
@@ -1257,7 +1344,7 @@
 
     // UI update and Broadcast Intent
     private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
-        if (!mVoiceCapable && (streamType == AudioSystem.STREAM_RING)) {
+        if (!isPlatformVoice() && (streamType == AudioSystem.STREAM_RING)) {
             streamType = AudioSystem.STREAM_NOTIFICATION;
         }
 
@@ -1346,10 +1433,14 @@
         }
 
         if (isStreamAffectedByMute(streamType)) {
-            if (streamType == AudioSystem.STREAM_MUSIC && mHdmiTvClient != null) {
-                synchronized (mHdmiTvClient) {
-                    if (mHdmiSystemAudioSupported) {
-                        mHdmiTvClient.setSystemAudioMute(state);
+            if (mHdmiManager != null) {
+                synchronized (mHdmiManager) {
+                    if (streamType == AudioSystem.STREAM_MUSIC && mHdmiTvClient != null) {
+                        synchronized (mHdmiTvClient) {
+                            if (mHdmiSystemAudioSupported) {
+                                mHdmiTvClient.setSystemAudioMute(state);
+                            }
+                        }
                     }
                 }
             }
@@ -1472,11 +1563,15 @@
 
     /** @see AudioManager#getMasterStreamType()  */
     public int getMasterStreamType() {
-        if (mVoiceCapable) {
-            return AudioSystem.STREAM_RING;
-        } else {
-            return AudioSystem.STREAM_NOTIFICATION;
+        switch (mPlatformType) {
+            case PLATFORM_VOICE:
+                return AudioSystem.STREAM_RING;
+            case PLATFORM_TELEVISION:
+                return AudioSystem.STREAM_MUSIC;
+            default:
+                break;
         }
+        return AudioSystem.STREAM_NOTIFICATION;
     }
 
     /** @see AudioManager#setMicrophoneMute(boolean) */
@@ -1504,7 +1599,7 @@
 
     /** @see AudioManager#setRingerMode(int) */
     public void setRingerMode(int ringerMode) {
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || isPlatformTelevision()) {
             return;
         }
 
@@ -1534,7 +1629,7 @@
                     ringerMode == AudioManager.RINGER_MODE_NORMAL) {
                     // ring and notifications volume should never be 0 when not silenced
                     // on voice capable devices
-                    if (mVoiceCapable &&
+                    if (isPlatformVoice() &&
                             mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
                         synchronized (mStreamStates[streamType]) {
                             Set set = mStreamStates[streamType].mIndex.entrySet();
@@ -2041,6 +2136,7 @@
         // muted by ringer mode have the correct volume
         setRingerModeInt(getRingerMode(), false);
 
+        checkAllFixedVolumeDevices();
         checkAllAliasStreamVolumes();
 
         synchronized (mSafeMediaVolumeState) {
@@ -2760,11 +2856,18 @@
                                         (1 << AudioSystem.STREAM_NOTIFICATION)|
                                         (1 << AudioSystem.STREAM_SYSTEM);
 
-        if (mVoiceCapable) {
-            ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC);
-        } else {
-            ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC);
+        switch (mPlatformType) {
+            case PLATFORM_VOICE:
+                ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC);
+                break;
+            case PLATFORM_TELEVISION:
+                ringerModeAffectedStreams = 0;
+                break;
+            default:
+                ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC);
+                break;
         }
+
         synchronized (mCameraSoundForced) {
             if (mCameraSoundForced) {
                 ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
@@ -2833,7 +2936,8 @@
     }
 
     private int getActiveStreamType(int suggestedStreamType) {
-        if (mVoiceCapable) {
+        switch (mPlatformType) {
+        case PLATFORM_VOICE:
             if (isInCommunication()) {
                 if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
                         == AudioSystem.FORCE_BT_SCO) {
@@ -2863,12 +2967,26 @@
                 if (DEBUG_VOL)
                     Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
                 return AudioSystem.STREAM_MUSIC;
-            } else {
-                if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
-                        + suggestedStreamType);
-                return suggestedStreamType;
             }
-        } else {
+            break;
+        case PLATFORM_TELEVISION:
+            if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+                if (isAfMusicActiveRecently(DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) {
+                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC");
+                    return AudioSystem.STREAM_MUSIC;
+                } else if (mMediaFocusControl.checkUpdateRemoteStateIfActive(
+                                                                        AudioSystem.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;
+                }
+            }
+            break;
+        default:
             if (isInCommunication()) {
                 if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
                         == AudioSystem.FORCE_BT_SCO) {
@@ -2899,12 +3017,12 @@
                             "getActiveStreamType: using STREAM_NOTIFICATION as default");
                     return AudioSystem.STREAM_NOTIFICATION;
                 }
-            } else {
-                if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
-                        + suggestedStreamType);
-                return suggestedStreamType;
             }
+            break;
         }
+        if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
+                + suggestedStreamType);
+        return suggestedStreamType;
     }
 
     private void broadcastRingerMode(int ringerMode) {
@@ -3098,13 +3216,7 @@
                         continue;
                     }
 
-                    // ignore settings for fixed volume devices: volume should always be at max or 0
-                    if ((mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) &&
-                            ((device & mFixedVolumeDevices) != 0)) {
-                        mIndex.put(device, (index != 0) ? mIndexMax : 0);
-                    } else {
-                        mIndex.put(device, getValidIndex(10 * index));
-                    }
+                    mIndex.put(device, getValidIndex(10 * index));
                 }
             }
         }
@@ -3263,6 +3375,25 @@
             return mStreamType;
         }
 
+        public void checkFixedVolumeDevices() {
+            synchronized (VolumeStreamState.class) {
+                // ignore settings for fixed volume devices: volume should always be at max or 0
+                if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
+                    Set set = mIndex.entrySet();
+                    Iterator i = set.iterator();
+                    while (i.hasNext()) {
+                        Map.Entry entry = (Map.Entry)i.next();
+                        int device = ((Integer)entry.getKey()).intValue();
+                        int index = ((Integer)entry.getValue()).intValue();
+                        if (((device & mFixedVolumeDevices) != 0) && index != 0) {
+                            entry.setValue(mIndexMax);
+                        }
+                        applyDeviceVolume(device);
+                    }
+                }
+            }
+        }
+
         private int getValidIndex(int index) {
             if (index < 0) {
                 return 0;
@@ -3469,6 +3600,9 @@
             if (mUseFixedVolume) {
                 return;
             }
+            if (isPlatformTelevision() && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
+                return;
+            }
             System.putIntForUser(mContentResolver,
                       streamState.getSettingNameForDevice(device),
                       (streamState.getIndex(device) + 5)/ 10,
@@ -3825,11 +3959,13 @@
                                 mDockAudioMediaEnabled ?
                                         AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE);
                     }
-
-                    if (mHdmiTvClient != null) {
-                        setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
+                    if (mHdmiManager != null) {
+                        synchronized (mHdmiManager) {
+                            if (mHdmiTvClient != null) {
+                                setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
+                            }
+                        }
                     }
-
                     // indicate the end of reconfiguration phase to audio HAL
                     AudioSystem.setParameters("restarting=false");
                     break;
@@ -4275,6 +4411,27 @@
                             null,
                             MUSIC_ACTIVE_POLL_PERIOD_MS);
                 }
+                // Television devices without CEC service apply software volume on HDMI output
+                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
+                    mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
+                    checkAllFixedVolumeDevices();
+                    if (mHdmiManager != null) {
+                        synchronized (mHdmiManager) {
+                            if (mHdmiPlaybackClient != null) {
+                                mHdmiCecSink = false;
+                                mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+                            }
+                        }
+                    }
+                }
+            } else {
+                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
+                    if (mHdmiManager != null) {
+                        synchronized (mHdmiManager) {
+                            mHdmiCecSink = false;
+                        }
+                    }
+                }
             }
             if (!isUsb && (device != AudioSystem.DEVICE_IN_WIRED_HEADSET)) {
                 sendDeviceConnectionIntent(device, state, name);
@@ -4577,18 +4734,20 @@
                     if (cameraSoundForced != mCameraSoundForced) {
                         mCameraSoundForced = cameraSoundForced;
 
-                        VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
-                        if (cameraSoundForced) {
-                            s.setAllIndexesToMax();
-                            mRingerModeAffectedStreams &=
-                                    ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
-                        } else {
-                            s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]);
-                            mRingerModeAffectedStreams |=
-                                    (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+                        if (!isPlatformTelevision()) {
+                            VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
+                            if (cameraSoundForced) {
+                                s.setAllIndexesToMax();
+                                mRingerModeAffectedStreams &=
+                                        ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+                            } else {
+                                s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]);
+                                mRingerModeAffectedStreams |=
+                                        (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+                            }
+                            // take new state into account for streams muted by ringer mode
+                            setRingerModeInt(getRingerMode(), false);
                         }
-                        // take new state into account for streams muted by ringer mode
-                        setRingerModeInt(getRingerMode(), false);
 
                         sendMsg(mAudioHandler,
                                 MSG_SET_FORCE_USE,
@@ -4804,27 +4963,57 @@
     // to HdmiControlService so that audio recevier can handle volume change.
     //==========================================================================================
 
+    private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback {
+        public void onComplete(int status) {
+            if (mHdmiManager != null) {
+                synchronized (mHdmiManager) {
+                    mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN);
+                    // Television devices without CEC service apply software volume on HDMI output
+                    if (isPlatformTelevision() && !mHdmiCecSink) {
+                        mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
+                    }
+                    checkAllFixedVolumeDevices();
+                }
+            }
+        }
+    };
+
     // If HDMI-CEC system audio is supported
     private boolean mHdmiSystemAudioSupported = false;
     // Set only when device is tv.
     private HdmiTvClient mHdmiTvClient;
+    // true if the device has system feature PackageManager.FEATURE_TELEVISION.
+    // cached HdmiControlManager interface
+    private HdmiControlManager mHdmiManager;
+    // Set only when device is a set-top box.
+    private HdmiPlaybackClient mHdmiPlaybackClient;
+    // true if we are a set-top box, an HDMI sink is connected and it supports CEC.
+    private boolean mHdmiCecSink;
+
+    private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback();
 
     @Override
     public int setHdmiSystemAudioSupported(boolean on) {
-        if (mHdmiTvClient == null) {
-            Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode.");
-            return AudioSystem.DEVICE_NONE;
-        }
+        int device = AudioSystem.DEVICE_NONE;
+        if (mHdmiManager != null) {
+            synchronized (mHdmiManager) {
+                if (mHdmiTvClient == null) {
+                    Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode.");
+                    return device;
+                }
 
-        synchronized (mHdmiTvClient) {
-            if (mHdmiSystemAudioSupported == on) {
-                return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
+                synchronized (mHdmiTvClient) {
+                    if (mHdmiSystemAudioSupported != on) {
+                        mHdmiSystemAudioSupported = on;
+                        AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
+                                on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED :
+                                     AudioSystem.FORCE_NONE);
+                    }
+                    device = AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
+                }
             }
-            mHdmiSystemAudioSupported = on;
-            AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
-                    on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED : AudioSystem.FORCE_NONE);
         }
-        return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
+        return device;
     }
 
     //==========================================================================================