CEC: Bug fixes for vendor-specific command handling

- Add sendStandby()
- Respond with <Feature Abort>[INCORRECT_MODE] when the listener
  is not ready

Bug: 17379243
Bug: 17358887
Change-Id: I26a4157a70f11206978763fbbe69e4190e3e1d5c
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 602f866..4866a9a 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -62,6 +62,7 @@
     void sendVendorCommand(int deviceType, int targetAddress, in byte[] params,
             boolean hasVendorId);
     void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType);
+    void sendStandby(int deviceType, int deviceId);
     void setHdmiRecordListener(IHdmiRecordListener callback);
     void startOneTouchRecord(int recorderAddress, in byte[] recordSource);
     void stopOneTouchRecord(int recorderAddress);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 2f36181..41ac589 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -517,8 +517,12 @@
     }
 
     protected boolean handleVendorCommand(HdmiCecMessage message) {
-        mService.invokeVendorCommandListeners(mDeviceType, message.getSource(),
-                message.getParams(), false);
+        if (!mService.invokeVendorCommandListeners(mDeviceType, message.getSource(),
+                message.getParams(), false)) {
+            // Vendor command listener may not have been registered yet. Respond with
+            // <Feature Abort> [NOT_IN_CORRECT_MODE] so that the sender can try again later.
+            mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
+        }
         return true;
     }
 
@@ -526,7 +530,10 @@
         byte[] params = message.getParams();
         int vendorId = HdmiUtils.threeBytesToInt(params);
         if (vendorId == mService.getVendorId()) {
-            mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), params, true);
+            if (!mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), params,
+                    true)) {
+                mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
+            }
         } else if (message.getDestination() != Constants.ADDR_BROADCAST &&
                 message.getSource() != Constants.ADDR_UNREGISTERED) {
             Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
@@ -537,6 +544,10 @@
         return true;
     }
 
+    protected void sendStandby(int deviceId) {
+        // Do nothing.
+    }
+
     protected boolean handleSetOsdName(HdmiCecMessage message) {
         // The default behavior of <Set Osd Name> is doing nothing.
         return true;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index c9b7b45..9033c38 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1652,6 +1652,16 @@
     }
 
     @Override
+    protected void sendStandby(int deviceId) {
+        HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
+        if (targetDevice == null) {
+            return;
+        }
+        int targetAddress = targetDevice.getLogicalAddress();
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
+    }
+
+    @Override
     protected void dump(final IndentingPrintWriter pw) {
         super.dump(pw);
         pw.println("mArcEstablished: " + mArcEstablished);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 55eb180..9a4000c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1345,6 +1345,22 @@
         }
 
         @Override
+        public void sendStandby(final int deviceType, final int deviceId) {
+            enforceAccessPermission();
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
+                    if (device == null) {
+                        Slog.w(TAG, "Local device not available");
+                        return;
+                    }
+                    device.sendStandby(deviceId);
+                }
+            });
+        }
+
+        @Override
         public void setHdmiRecordListener(IHdmiRecordListener listener) {
             HdmiControlService.this.setHdmiRecordListener(listener);
         }
@@ -1869,9 +1885,12 @@
         }
     }
 
-    void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
+    boolean invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
             boolean hasVendorId) {
         synchronized (mLock) {
+            if (mVendorCommandListenerRecords.isEmpty()) {
+                return false;
+            }
             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
                 if (record.mDeviceType != deviceType) {
                     continue;
@@ -1882,6 +1901,7 @@
                     Slog.e(TAG, "Failed to notify vendor command reception", e);
                 }
             }
+            return true;
         }
     }