HDMI: TV should ignore <Standby> from non-AS devices

Source devices can send the TV to sleep by sending <Standby> even when they are not the active source.
This patch avoids this issue by ignoring <Standby> messages from non-active source devices.

It is possible that the active source known by the TV panel is
not the true active source, but the chances are much lower than OTT
knowing a wrong active source. Otherwise, many other bugs would've
occured on ATV CEC implementation about TV panels knowing a wrong AS.

Bug: 322298325
Bug: 308832215
Test: atest HdmiCecLocalDeviceTvTest
Change-Id: I4dc00ebd99ddb8f9393424749d9117a9e76b32bf
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 34e75c0..46061a5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -482,6 +482,22 @@
     @Override
     @ServiceThreadOnly
     @Constants.HandleMessageResult
+    protected int handleStandby(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+
+        // Ignore <Standby> from non-active source device.
+        if (getActiveSource().logicalAddress != message.getSource()) {
+            Slog.d(TAG, "<Standby> was not sent by the current active source, ignoring."
+                    + " Current active source has logical address "
+                    + getActiveSource().logicalAddress);
+            return Constants.HANDLED;
+        }
+        return super.handleStandby(message);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    @Constants.HandleMessageResult
     protected int handleInactiveSource(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // Seq #10
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 5e38010..67ae998 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -170,6 +170,11 @@
                     }
 
                     @Override
+                    boolean isPowerOnOrTransient() {
+                        return true;
+                    }
+
+                    @Override
                     void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
                         mDeviceEventListeners.add(new DeviceEventListener(device, status));
                     }
@@ -1839,4 +1844,38 @@
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
     }
+
+    @Test
+    public void handleStandby_fromActiveSource_standby() {
+        mPowerManager.setInteractive(true);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_1, 0x1000,
+                "HdmiCecLocalDeviceTvTest");
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
+                ADDR_TV);
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(standbyMessage))
+                .isEqualTo(Constants.HANDLED);
+        mTestLooper.dispatchAll();
+
+        assertThat(mPowerManager.isInteractive()).isFalse();
+    }
+
+    @Test
+    public void handleStandby_fromNonActiveSource_noStandby() {
+        mPowerManager.setInteractive(true);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_2, 0x2000,
+                "HdmiCecLocalDeviceTvTest");
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
+                ADDR_TV);
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(standbyMessage))
+                .isEqualTo(Constants.HANDLED);
+        mTestLooper.dispatchAll();
+
+        assertThat(mPowerManager.isInteractive()).isTrue();
+    }
 }