Refactor <Feature Abort> logic to concentrate it in one place.

- Don't reply from the unregistered address.
- Use "unrecognized opcode" as the default reason.

Bug: 16799466, Bug: 16798785
Change-Id: I7c2ece6436f7ebd59986d2baf4f45cd86e6622d9
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index cbccc1d..c33b35f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -502,24 +502,30 @@
     @ServiceThreadOnly
     private void onReceiveCommand(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        if (isAcceptableAddress(message.getDestination())
-                && mService.handleCecCommand(message)) {
+        if (isAcceptableAddress(message.getDestination()) && mService.handleCecCommand(message)) {
             return;
         }
-        if (message.getDestination() == Constants.ADDR_BROADCAST) {
-            return;
-        }
-        if (message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) {
-            Slog.v(TAG, "Unhandled <Feature Abort> message:" + message);
-            return;
-        }
+        // Not handled message, so we will reply it with <Feature Abort>.
+        maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+    }
 
-        int sourceAddress = message.getDestination();
-        // Reply <Feature Abort> to initiator (source) for all requests.
-        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
-                sourceAddress, message.getSource(), message.getOpcode(),
-                Constants.ABORT_REFUSED);
-        sendCommand(cecMessage);
+    @ServiceThreadOnly
+    void maySendFeatureAbortCommand(HdmiCecMessage message, int reason) {
+        assertRunOnServiceThread();
+        // Swap the source and the destination.
+        int src = message.getDestination();
+        int dest = message.getSource();
+        if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_UNREGISTERED) {
+            // Don't reply <Feature Abort> from the unregistered devices or for the broadcasted
+            // messages. See CEC 12.2 Protocol General Rules for detail.
+            return;
+        }
+        int originalOpcode = message.getOpcode();
+        if (originalOpcode == Constants.MESSAGE_FEATURE_ABORT) {
+            return;
+        }
+        sendCommand(
+                HdmiCecMessageBuilder.buildFeatureAbortCommand(src, dest, originalOpcode, reason));
     }
 
     @ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 55f6ade..cf16fa3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -308,11 +308,8 @@
     protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
         assertRunOnServiceThread();
         Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
-        mService.sendCecCommand(
-                HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
-                        message.getSource(), Constants.MESSAGE_GET_MENU_LANGUAGE,
-                        Constants.ABORT_UNRECOGNIZED_OPCODE));
-        return true;
+        // 'return false' will cause to reply with <Feature Abort>.
+        return false;
     }
 
     @ServiceThreadOnly
@@ -431,9 +428,7 @@
         } else if (message.getDestination() != Constants.ADDR_BROADCAST &&
                 message.getSource() != Constants.ADDR_UNREGISTERED) {
             Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
-            mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
-                    message.getSource(), Constants.MESSAGE_VENDOR_COMMAND_WITH_ID,
-                    Constants.ABORT_UNRECOGNIZED_OPCODE));
+            mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
         } else {
             Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
         }
@@ -448,8 +443,7 @@
     protected boolean handleRecordTvScreen(HdmiCecMessage message) {
         // The default behavior of <Record TV Screen> is replying <Feature Abort> with
         // "Cannot provide source".
-        mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
-                message.getSource(), message.getOpcode(), Constants.ABORT_CANNOT_PROVIDE_SOURCE));
+        mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
         return true;
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
index c653125..8c40424 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
@@ -48,16 +48,7 @@
         }
         @Override
         public boolean handle(HdmiCecMessage message) {
-            int src = message.getSource();
-            int dest = message.getDestination();
-            if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_BROADCAST) {
-                // Do not send <Feature Abort> on the message from the unassigned device
-                // or the broadcasted message.
-                return true;
-            }
-            HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
-                    dest, src, message.getOpcode(), mReason);
-            mService.sendCecCommand(cecMessage);
+            mService.maySendFeatureAbortCommand(message, mReason);
             return true;
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e0673e9..fe55b01 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -532,6 +532,18 @@
         mCecController.sendCommand(command, null);
     }
 
+    /**
+     * Send <Feature Abort> command on the given CEC message if possible.
+     * If the aborted message is invalid, then it wont send the message.
+     * @param command original command to be aborted
+     * @param reason reason of feature abort
+     */
+    @ServiceThreadOnly
+    void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
+        assertRunOnServiceThread();
+        mCecController.maySendFeatureAbortCommand(command, reason);
+    }
+
     @ServiceThreadOnly
     boolean handleCecCommand(HdmiCecMessage message) {
         assertRunOnServiceThread();