Merge "Add SystemAudioAutoInitiationAction and SystemAudioStatusAction"
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 92ddd3d..18a3bbf 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -52,6 +52,8 @@
 
     HdmiCecLocalDeviceTv(HdmiControlService service) {
         super(service, HdmiCec.DEVICE_TV);
+
+        // TODO: load system audio mode and set it to mSystemAudioMode.
     }
 
     @Override
@@ -174,6 +176,15 @@
                         }
 
                         addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
+
+                        // If there is AVR, initiate System Audio Auto initiation action,
+                        // which turns on and off system audio according to last system
+                        // audio setting.
+                        HdmiCecDeviceInfo avrInfo = getAvrDeviceInfo();
+                        if (avrInfo != null) {
+                            addAndStartAction(new SystemAudioAutoInitiationAction(
+                                    HdmiCecLocalDeviceTv.this, avrInfo.getLogicalAddress()));
+                        }
                     }
                 });
         addAndStartAction(action);
@@ -456,4 +467,10 @@
             hotplugActions.get(0).pollAllDevicesNow();
         }
     }
+
+    boolean canChangeSystemAudio() {
+        // TODO: implement this.
+        // return true if no system audio control sequence is running.
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 8dbfd85..361a063 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -377,6 +377,17 @@
         return buildCommand(src, dest, HdmiCec.MESSAGE_USER_CONTROL_RELEASED);
     }
 
+    /**
+     * Build <Give System Audio Mode Status> command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildGiveSystemAudioModeStatus(int src, int dest) {
+        return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS);
+    }
+
     /***** Please ADD new buildXXX() methods above. ******/
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiConstants.java b/services/core/java/com/android/server/hdmi/HdmiConstants.java
index 9c60e55..5294506 100644
--- a/services/core/java/com/android/server/hdmi/HdmiConstants.java
+++ b/services/core/java/com/android/server/hdmi/HdmiConstants.java
@@ -95,5 +95,7 @@
     static final int POLL_ITERATION_IN_ORDER = 0x10000;
     static final int POLL_ITERATION_REVERSE_ORDER = 0x20000;
 
+    static final int UNKNOWN_VOLUME = -1;
+
     private HdmiConstants() { /* cannot be instantiated */ }
 }
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
new file mode 100644
index 0000000..e4d82ef
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+
+import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
+/**
+ * Action to initiate system audio once AVR is detected on Device discovery action.
+ */
+final class SystemAudioAutoInitiationAction extends FeatureAction {
+    private final int mAvrAddress;
+
+    // State that waits for <System Audio Mode Status> once send
+    // <Give System Audio Mode Status> to AV Receiver.
+    private static final int STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS = 1;
+
+    SystemAudioAutoInitiationAction(HdmiCecLocalDevice source, int avrAddress) {
+        super(source);
+        mAvrAddress = avrAddress;
+    }
+
+    @Override
+    boolean start() {
+        mState = STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS;
+
+        addTimer(mState, TIMEOUT_MS);
+        sendGiveSystemAudioModeStatus();
+        return true;
+    }
+
+    private void sendGiveSystemAudioModeStatus() {
+        sendCommand(HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(getSourceAddress(),
+                mAvrAddress), new SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error != HdmiConstants.SEND_RESULT_SUCCESS) {
+                    tv().setSystemAudioMode(false);
+                    finish();
+                }
+            }
+        });
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS) {
+            return false;
+        }
+
+        switch (cmd.getOpcode()) {
+            case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
+                handleSystemAudioModeStatusMessage();
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void handleSystemAudioModeStatusMessage() {
+        // If the last setting is system audio, turn on system audio whatever AVR status is.
+        if (tv().getSystemAudioMode()) {
+            if (canChangeSystemAudio()) {
+                addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, true));
+            }
+        } else {
+            // If the last setting is non-system audio, turn off system audio mode
+            // and update system audio status (volume or mute).
+            tv().setSystemAudioMode(false);
+            if (canChangeSystemAudio()) {
+                addAndStartAction(new SystemAudioStatusAction(tv(), mAvrAddress));
+            }
+        }
+        finish();
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        }
+
+        switch (mState) {
+            case STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS:
+                handleSystemAudioModeStatusTimeout();
+                break;
+        }
+    }
+
+    private void handleSystemAudioModeStatusTimeout() {
+        if (tv().getSystemAudioMode()) {
+            if (canChangeSystemAudio()) {
+                addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, true));
+            }
+        } else {
+            tv().setSystemAudioMode(false);
+        }
+        finish();
+    }
+
+    private boolean canChangeSystemAudio() {
+        return tv().canChangeSystemAudio();
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
new file mode 100644
index 0000000..75e4fef
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
+/**
+ * Action to update audio status (volume or mute) of audio amplifier
+ */
+// TODO: refactor SystemAudioMode so that it uses this class instead of internal state.
+final class SystemAudioStatusAction extends FeatureAction {
+    private static final String TAG = "SystemAudioStatusAction";
+
+    // State that waits for <ReportAudioStatus>.
+    private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 1;
+
+    private final int mAvrAddress;
+
+    SystemAudioStatusAction(HdmiCecLocalDevice source, int avrAddress) {
+        super(source);
+        mAvrAddress = avrAddress;
+    }
+
+    @Override
+    boolean start() {
+        mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
+        addTimer(mState, TIMEOUT_MS);
+        sendGiveAudioStatus();
+        return true;
+    }
+
+    private void sendGiveAudioStatus() {
+        sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mAvrAddress),
+                new SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error != HdmiConstants.SEND_RESULT_SUCCESS) {
+                    handleSendGiveAudioStatusFailure();
+                }
+            }
+        });
+    }
+
+    private void handleSendGiveAudioStatusFailure() {
+        // Inform to all application that the audio status (volumn, mute) of
+        // the audio amplifier is unknown.
+        tv().setAudioStatus(false, HdmiConstants.UNKNOWN_VOLUME);
+
+        int uiCommand = tv().getSystemAudioMode()
+                ? HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION  // SystemAudioMode: ON
+                : HdmiConstants.UI_COMMAND_MUTE_FUNCTION;           // SystemAudioMode: OFF
+        sendUserControlPressedAndReleased(uiCommand);
+        finish();
+    }
+
+    private void sendUserControlPressedAndReleased(int uiCommand) {
+        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
+                getSourceAddress(), mAvrAddress, uiCommand));
+        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
+                getSourceAddress(), mAvrAddress));
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS) {
+            return false;
+        }
+
+        switch (cmd.getOpcode()) {
+            case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
+                handleReportAudioStatus(cmd);
+                return true;
+        }
+
+        return false;
+    }
+
+    private void handleReportAudioStatus(HdmiCecMessage cmd) {
+        byte[] params = cmd.getParams();
+        if (params.length > 0) {
+            boolean mute = (params[0] & 0x80) == 0x80;
+            int volume = params[0] & 0x7F;
+            tv().setAudioStatus(mute, volume);
+
+            if ((tv().getSystemAudioMode() && mute) || (!tv().getSystemAudioMode() && !mute)) {
+                // Toggle AVR's mute status to match with the system audio status.
+                sendUserControlPressedAndReleased(HdmiConstants.UI_COMMAND_MUTE);
+            }
+            finish();
+        } else {
+            Slog.e(TAG, "Invalid <Report Audio Status> message:" + cmd);
+            handleSendGiveAudioStatusFailure();
+            return;
+        }
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        }
+
+        handleSendGiveAudioStatusFailure();
+    }
+}