diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 090d728aedc88..77213bf256a66 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -601,6 +601,7 @@ final class Constants { }) @interface RcProfileSource {} + static final int HDMI_EARC_STATUS_UNKNOWN = -1; static final int HDMI_EARC_STATUS_IDLE = IEArcStatus.IDLE; // IDLE1 static final int HDMI_EARC_STATUS_EARC_PENDING = IEArcStatus.EARC_PENDING; // DISC1 and DISC2 @@ -609,6 +610,7 @@ final class Constants { IEArcStatus.EARC_CONNECTED; // eARC connected @IntDef({ + HDMI_EARC_STATUS_UNKNOWN, HDMI_EARC_STATUS_IDLE, HDMI_EARC_STATUS_EARC_PENDING, HDMI_EARC_STATUS_ARC_PENDING, diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java index 9437c4f5aee8a..7ae7d5c6de1f6 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java @@ -16,6 +16,11 @@ package com.android.server.hdmi; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_IDLE; + import android.stats.hdmi.HdmiStatsEnums; import com.android.internal.annotations.VisibleForTesting; @@ -192,6 +197,46 @@ public class HdmiCecAtomWriter { ); } + /** + * Writes a HdmiEarcStatusReported atom representing a eARC status change. + * @param isSupported Whether the hardware supports eARC. + * @param isEnabled Whether eARC is enabled. + * @param oldConnectionState If enumLogReason == HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED, + * the state just before the change. Otherwise, the current state. + * @param newConnectionState If enumLogReason == HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED, + * the state just after the change. Otherwise, the current state. + * @param enumLogReason The event that triggered the log. + */ + public void earcStatusChanged(boolean isSupported, boolean isEnabled, int oldConnectionState, + int newConnectionState, int enumLogReason) { + int enumOldConnectionState = earcStateToEnum(oldConnectionState); + int enumNewConnectionState = earcStateToEnum(newConnectionState); + + FrameworkStatsLog.write( + FrameworkStatsLog.HDMI_EARC_STATUS_REPORTED, + isSupported, + isEnabled, + enumOldConnectionState, + enumNewConnectionState, + enumLogReason + ); + } + + private int earcStateToEnum(int earcState) { + switch (earcState) { + case HDMI_EARC_STATUS_IDLE: + return HdmiStatsEnums.HDMI_EARC_STATUS_IDLE; + case HDMI_EARC_STATUS_EARC_PENDING: + return HdmiStatsEnums.HDMI_EARC_STATUS_EARC_PENDING; + case HDMI_EARC_STATUS_ARC_PENDING: + return HdmiStatsEnums.HDMI_EARC_STATUS_ARC_PENDING; + case HDMI_EARC_STATUS_EARC_CONNECTED: + return HdmiStatsEnums.HDMI_EARC_STATUS_EARC_CONNECTED; + default: + return HdmiStatsEnums.HDMI_EARC_STATUS_UNKNOWN; + } + } + /** * Contains the required arguments for creating any HdmiCecMessageReported atom */ diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 3eab4b0b1835a..1034b0a36781d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -28,6 +28,7 @@ import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; import static com.android.server.hdmi.Constants.DISABLED; import static com.android.server.hdmi.Constants.ENABLED; import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN; import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE; import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING; import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE; @@ -91,6 +92,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings.Global; +import android.stats.hdmi.HdmiStatsEnums; import android.sysprop.HdmiProperties; import android.text.TextUtils; import android.util.ArrayMap; @@ -3530,7 +3532,8 @@ public class HdmiControlService extends SystemService { } } - private boolean isEarcSupported() { + @VisibleForTesting + protected boolean isEarcSupported() { synchronized (mLock) { return mEarcSupported; } @@ -3648,6 +3651,11 @@ public class HdmiControlService extends SystemService { setEarcEnabledInHal(false, false); } } + if (isTvDevice()) { + int earcStatus = getEarcStatus(); + getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(), + earcStatus, earcStatus, HdmiStatsEnums.LOG_REASON_WAKE); + } // TODO: Initialize MHL local devices. } @@ -4677,6 +4685,15 @@ public class HdmiControlService extends SystemService { mEarcLocalDevice = localDevice; } + @ServiceThreadOnly + private int getEarcStatus() { + assertRunOnServiceThread(); + if (mEarcLocalDevice != null) { + return mEarcLocalDevice.mEarcStatus; + } + return HDMI_EARC_STATUS_UNKNOWN; + } + @ServiceThreadOnly @VisibleForTesting HdmiEarcLocalDevice getEarcLocalDevice() { @@ -4729,13 +4746,18 @@ public class HdmiControlService extends SystemService { Slog.w(TAG, "Tried to update eARC status on a port that doesn't support eARC."); return; } + int oldEarcStatus = getEarcStatus(); if (mEarcLocalDevice != null) { mEarcLocalDevice.handleEarcStateChange(status); + getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(), + oldEarcStatus, status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED); } else if (status == HDMI_EARC_STATUS_ARC_PENDING) { // If eARC is disabled, the local device is null. This is why we notify // AudioService here that the eARC connection is terminated. notifyEarcStatusToAudioService(false, new ArrayList<>()); startArcAction(true, null); + getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(), + oldEarcStatus, status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java index 5e54d3b8c2378..ffe088ca1e663 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java @@ -76,6 +76,8 @@ public class HdmiCecAtomLoggingTest { private TestLooper mTestLooper = new TestLooper(); private int mPhysicalAddress = 0x1110; private HdmiPortInfo[] mHdmiPortInfo; + private HdmiEarcController mHdmiEarcController; + private FakeEarcNativeWrapper mEarcNativeWrapper; @Before public void setUp() throws RemoteException { @@ -94,7 +96,6 @@ public class HdmiCecAtomLoggingTest { doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter(); - HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig(); @@ -118,6 +119,11 @@ public class HdmiCecAtomLoggingTest { mHdmiControlServiceSpy.setHdmiCecNetwork(mHdmiCecNetwork); mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper()); + mEarcNativeWrapper = new FakeEarcNativeWrapper(); + mHdmiEarcController = HdmiEarcController.createWithNativeWrapper( + mHdmiControlServiceSpy, mEarcNativeWrapper); + mHdmiControlServiceSpy.setEarcController(mHdmiEarcController); + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_OUTPUT, 0x0000) diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java new file mode 100644 index 0000000000000..24057578e62bc --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2018 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 static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN; +import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.ContextWrapper; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; +import android.os.Looper; +import android.os.RemoteException; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; +import android.stats.hdmi.HdmiStatsEnums; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.SystemService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mockito; + +import java.util.Collections; + +/** + * Tests for the {@link HdmiCecAtomWriter} class and its usage by the HDMI-CEC framework. + */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class HdmiCecAtomLoggingTvTest { + private HdmiCecAtomWriter mHdmiCecAtomWriterSpy; + private HdmiControlService mHdmiControlServiceSpy; + private HdmiCecController mHdmiCecController; + private HdmiMhlControllerStub mHdmiMhlControllerStub; + private FakeNativeWrapper mNativeWrapper; + private FakePowerManagerWrapper mPowerManager; + private HdmiCecNetwork mHdmiCecNetwork; + private Looper mLooper; + private Context mContextSpy; + private TestLooper mTestLooper = new TestLooper(); + private int mPhysicalAddress = 0x0000; + private static final int EARC_PORT_ID = 1; + private HdmiEarcController mHdmiEarcController; + private FakeEarcNativeWrapper mEarcNativeWrapper; + + @Before + public void setUp() throws RemoteException { + mHdmiCecAtomWriterSpy = spy(new HdmiCecAtomWriter()); + + mLooper = mTestLooper.getLooper(); + + mContextSpy = spy(new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + + FakeAudioFramework audioFramework = new FakeAudioFramework(); + + mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, + Collections.singletonList(HdmiDeviceInfo.DEVICE_TV), + audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager())); + doNothing().when(mHdmiControlServiceSpy) + .writeStringSystemProperty(anyString(), anyString()); + doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter(); + + HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); + doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig(); + + mHdmiControlServiceSpy.setIoLooper(mLooper); + mHdmiControlServiceSpy.setCecMessageBuffer( + new CecMessageBuffer(mHdmiControlServiceSpy)); + + mNativeWrapper = new FakeNativeWrapper(); + mNativeWrapper.setPhysicalAddress(mPhysicalAddress); + + mHdmiCecController = HdmiCecController.createWithNativeWrapper( + mHdmiControlServiceSpy, mNativeWrapper, mHdmiCecAtomWriterSpy); + mHdmiControlServiceSpy.setCecController(mHdmiCecController); + + mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlServiceSpy); + mHdmiControlServiceSpy.setHdmiMhlController( + mHdmiMhlControllerStub); + + mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlServiceSpy, + mHdmiCecController, mHdmiMhlControllerStub); + mHdmiControlServiceSpy.setHdmiCecNetwork(mHdmiCecNetwork); + mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper()); + + mEarcNativeWrapper = new FakeEarcNativeWrapper(); + mHdmiEarcController = HdmiEarcController.createWithNativeWrapper( + mHdmiControlServiceSpy, mEarcNativeWrapper); + mHdmiControlServiceSpy.setEarcController(mHdmiEarcController); + + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; + hdmiPortInfos[0] = + new HdmiPortInfo.Builder(EARC_PORT_ID, HdmiPortInfo.PORT_OUTPUT, 0x0000) + .setCecSupported(true) + .setMhlSupported(false) + .setArcSupported(false) + .setEarcSupported(true) + .build(); + mNativeWrapper.setPortInfo(hdmiPortInfos); + mNativeWrapper.setPortConnectionStatus(EARC_PORT_ID, true); + + mHdmiControlServiceSpy.initService(); + mPowerManager = new FakePowerManagerWrapper(mContextSpy); + mHdmiControlServiceSpy.setPowerManager(mPowerManager); + mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED); + mTestLooper.dispatchAll(); + + Mockito.reset(mHdmiCecAtomWriterSpy); + } + + @Test + public void testEarcStatusChanged_handleEarcStateChange_writesAtom() { + mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED); + mTestLooper.dispatchAll(); + Mockito.clearInvocations(mHdmiCecAtomWriterSpy); + + mHdmiControlServiceSpy.handleEarcStateChange(HDMI_EARC_STATUS_EARC_PENDING, + EARC_PORT_ID); + verify(mHdmiCecAtomWriterSpy, times(1)) + .earcStatusChanged(true, true, HDMI_EARC_STATUS_EARC_PENDING, + HDMI_EARC_STATUS_EARC_PENDING, + HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED); + } + + @Test + public void testEarcStatusChanged_onWakeUp_earcSupported_earcEnabled_writesAtom() { + mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED); + Mockito.clearInvocations(mHdmiCecAtomWriterSpy); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON); + verify(mHdmiCecAtomWriterSpy, times(1)) + .earcStatusChanged(true, true, HDMI_EARC_STATUS_EARC_PENDING, + HDMI_EARC_STATUS_EARC_PENDING, HdmiStatsEnums.LOG_REASON_WAKE); + } + + @Test + public void testEarcStatusChanged_onWakeUp_earcSupported_earcDisabled_writesAtom() { + mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED); + Mockito.clearInvocations(mHdmiCecAtomWriterSpy); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON); + verify(mHdmiCecAtomWriterSpy, times(1)) + .earcStatusChanged(true, false, HDMI_EARC_STATUS_UNKNOWN, + HDMI_EARC_STATUS_UNKNOWN, HdmiStatsEnums.LOG_REASON_WAKE); + } + + @Test + public void testEarcStatusChanged_onWakeUp_earcNotSupported_earcEnabled_writesAtom() { + doReturn(false).when(mHdmiControlServiceSpy).isEarcSupported(); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED); + Mockito.clearInvocations(mHdmiCecAtomWriterSpy); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON); + verify(mHdmiCecAtomWriterSpy, times(1)) + .earcStatusChanged(false, true, HDMI_EARC_STATUS_UNKNOWN, + HDMI_EARC_STATUS_UNKNOWN, HdmiStatsEnums.LOG_REASON_WAKE); + } + + @Test + public void testEarcStatusChanged_onWakeUp_earcNotSupported_earcDisabled_writesAtom() { + doReturn(false).when(mHdmiControlServiceSpy).isEarcSupported(); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED); + Mockito.clearInvocations(mHdmiCecAtomWriterSpy); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON); + verify(mHdmiCecAtomWriterSpy, times(1)) + .earcStatusChanged(false, false, HDMI_EARC_STATUS_UNKNOWN, + HDMI_EARC_STATUS_UNKNOWN, HdmiStatsEnums.LOG_REASON_WAKE); + } +}