Add SystemAudioInitiationActionFromAvr
To handle initiating the System Audio Control feature from an Amplifier
See CEC 13.15 for details.
Bug: 80297602
Test: make; local tests
Change-Id: I4772b6878bc1da816eea6c8e8b423c330315b1a8
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
index d26be57..11faa56 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
@@ -256,6 +256,10 @@
return (HdmiCecLocalDeviceTv) mSource;
}
+ protected final HdmiCecLocalDeviceAudioSystem audioSystem() {
+ return (HdmiCecLocalDeviceAudioSystem) mSource;
+ }
+
protected final int getSourceAddress() {
return mSource.getDeviceInfo().getLogicalAddress();
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 2ea46c7..37516d0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -18,7 +18,6 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioManager;
import android.os.SystemProperties;
-import android.provider.Settings.Global;
import com.android.internal.annotations.GuardedBy;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -252,6 +251,12 @@
}
}
+ protected boolean isSystemAudioActivated() {
+ synchronized (mLock) {
+ return mSystemAudioActivated;
+ }
+ }
+
/** Reports if System Audio Mode is supported by the connected TV */
interface TvSystemAudioModeSupportedCallback {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 37f96142..f1cb246 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -277,6 +277,16 @@
}
/**
+ * Build <Request Active Source> command.
+ *
+ * @param src source address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildRequestActiveSource(int src) {
+ return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REQUEST_ACTIVE_SOURCE);
+ }
+
+ /**
* Build <Active Source> command.
*
* @param src source address of command
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
index bc0bad1..d4932f9 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
@@ -13,13 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.server.hdmi;
-import android.util.Log;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+import com.android.internal.annotations.VisibleForTesting;
+/**
+ * Feature action that handles System Audio Mode initiated by AVR devices.
+ */
public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction {
- private static final String TAG = "SystemAudioInitFromAvr";
+
+ // State that waits for <Active Source> once send <Request Active Source>.
+ private static final int STATE_WAITING_FOR_ACTIVE_SOURCE = 1;
+ @VisibleForTesting
+ static final int MAX_RETRY_COUNT = 5;
+
+ private int mSendRequestActiveSourceRetryCount = 0;
+ private int mSendSetSystemAudioModeRetryCount = 0;
SystemAudioInitiationActionFromAvr(HdmiCecLocalDevice source) {
super(source);
@@ -27,18 +37,100 @@
@Override
boolean start() {
- Log.i(TAG, "start");
- return false;
+ if (audioSystem().mActiveSource.physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) {
+ mState = STATE_WAITING_FOR_ACTIVE_SOURCE;
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ sendRequestActiveSource();
+ } else {
+ queryTvSystemAudioModeSupport();
+ }
+ return true;
}
@Override
boolean processCommand(HdmiCecMessage cmd) {
- Log.i(TAG, "processCommand. cmd = " + cmd);
+ switch (cmd.getOpcode()) {
+ case Constants.MESSAGE_ACTIVE_SOURCE:
+ // received <Active Source>
+ if (mState != STATE_WAITING_FOR_ACTIVE_SOURCE) {
+ return false;
+ }
+ mActionTimer.clearTimerMessage();
+ int physicalAddress = HdmiUtils.twoBytesToInt(cmd.getParams());
+ if (physicalAddress != getSourcePath()) {
+ audioSystem().setActiveSource(cmd.getSource(), physicalAddress);
+ }
+ queryTvSystemAudioModeSupport();
+ return true;
+ }
return false;
}
@Override
void handleTimerEvent(int state) {
- Log.i(TAG, "handleTimerEvent. state = " + state);
+ if (mState != state) {
+ return;
+ }
+
+ switch (mState) {
+ case STATE_WAITING_FOR_ACTIVE_SOURCE:
+ handleActiveSourceTimeout();
+ break;
+ }
+ }
+
+ protected void sendRequestActiveSource() {
+ sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()),
+ result -> {
+ if (result != SendMessageResult.SUCCESS) {
+ if (mSendRequestActiveSourceRetryCount < MAX_RETRY_COUNT) {
+ mSendRequestActiveSourceRetryCount++;
+ sendRequestActiveSource();
+ } else {
+ audioSystem().setSystemAudioMode(false);
+ finish();
+ }
+ }
+ });
+ }
+
+ protected void sendSetSystemAudioMode(boolean on, int dest) {
+ sendCommand(HdmiCecMessageBuilder.buildSetSystemAudioMode(getSourceAddress(),
+ dest, on), result -> {
+ if (result != SendMessageResult.SUCCESS) {
+ if (mSendSetSystemAudioModeRetryCount < MAX_RETRY_COUNT) {
+ mSendSetSystemAudioModeRetryCount++;
+ sendSetSystemAudioMode(on, dest);
+ } else {
+ audioSystem().setSystemAudioMode(false);
+ finish();
+ }
+ }
+ });
+ }
+
+ private void handleActiveSourceTimeout() {
+ HdmiLogger.debug("Cannot get active source.");
+ audioSystem().setSystemAudioMode(false);
+ finish();
+ }
+
+ private void queryTvSystemAudioModeSupport() {
+ audioSystem().queryTvSystemAudioModeSupport(
+ supported -> {
+ if (supported) {
+ if (audioSystem().setSystemAudioMode(true)) {
+ sendSetSystemAudioMode(true, Constants.ADDR_BROADCAST);
+ }
+ finish();
+ } else {
+ audioSystem().setSystemAudioMode(false);
+ finish();
+ }
+ });
+ }
+
+ private void switchToRelevantInputForDeviceAt(int physicalAddress) {
+ // TODO(shubang): implement this method
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
new file mode 100644
index 0000000..ceac0ec6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.Nullable;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioManager;
+import android.os.Looper;
+import android.os.test.TestLooper;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link SystemAudioInitiationActionFromAvr}
+ */
+@SmallTest
+@RunWith(JUnit4.class)
+public class SystemAudioInitiationActionFromAvrTest {
+
+ private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
+ private TestLooper mTestLooper = new TestLooper();
+
+ private boolean mShouldDispatchActiveSource;
+ private boolean mTvSystemAudioModeSupport;
+ private int mTryCountBeforeSucceed;
+ private HdmiDeviceInfo mDeviceInfoForTests;
+
+ private int mMsgRequestActiveSourceCount;
+ private int mMsgSetSystemAudioModeCount;
+ private int mQueryTvSystemAudioModeSupportCount;
+
+ @Before
+ public void SetUp() {
+ mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234);
+ HdmiControlService hdmiControlService = new HdmiControlService(null) {
+
+ @Override
+ void sendCecCommand(HdmiCecMessage command,
+ @Nullable SendMessageCallback callback) {
+ switch (command.getOpcode()) {
+ case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE:
+ mMsgRequestActiveSourceCount++;
+ if (mTryCountBeforeSucceed >= mMsgRequestActiveSourceCount
+ && callback != null) {
+ callback.onSendCompleted(SendMessageResult.NACK);
+ break;
+ }
+ if (mShouldDispatchActiveSource) {
+ mHdmiCecLocalDeviceAudioSystem.dispatchMessage(
+ HdmiCecMessageBuilder.buildActiveSource(
+ Constants.ADDR_TV, 1002));
+ }
+ break;
+ case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
+ mMsgSetSystemAudioModeCount++;
+ if (mTryCountBeforeSucceed >= mMsgSetSystemAudioModeCount
+ && callback != null) {
+ callback.onSendCompleted(SendMessageResult.NACK);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected message");
+ }
+ }
+
+ @Override
+ AudioManager getAudioManager() {
+ return new AudioManager() {
+
+ @Override
+ public int setHdmiSystemAudioSupported(boolean on) {
+ return 0;
+ }
+ };
+ }
+
+ @Override
+ boolean isPowerStandby() {
+ return false;
+ }
+
+ @Override
+ boolean isAddressAllocated() {
+ return true;
+ }
+ };
+ mHdmiCecLocalDeviceAudioSystem =
+ new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
+ @Override
+ void queryTvSystemAudioModeSupport(
+ TvSystemAudioModeSupportedCallback callback) {
+ mQueryTvSystemAudioModeSupportCount++;
+ if (callback != null) {
+ callback.onResult(mTvSystemAudioModeSupport);
+ }
+ }
+
+ @Override
+ HdmiDeviceInfo getDeviceInfo() {
+ return mDeviceInfoForTests;
+ }
+ };
+ mHdmiCecLocalDeviceAudioSystem.init();
+ Looper looper = mTestLooper.getLooper();
+ hdmiControlService.setIoLooper(looper);
+ }
+
+ @Test
+ public void testNoActiveSourceMessageReceived() {
+ resetTestVariables();
+ mShouldDispatchActiveSource = false;
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress)
+ .isEqualTo(Constants.INVALID_PHYSICAL_ADDRESS);
+
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
+ new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem));
+ mTestLooper.dispatchAll();
+
+ assertThat(mMsgRequestActiveSourceCount).isEqualTo(1);
+ assertThat(mMsgSetSystemAudioModeCount).isEqualTo(0);
+ assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(0);
+ assertFalse(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated());
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress)
+ .isEqualTo(Constants.INVALID_PHYSICAL_ADDRESS);
+ }
+
+ @Test
+ public void testTvNotSupport() {
+ resetTestVariables();
+ mShouldDispatchActiveSource = true;
+ mTvSystemAudioModeSupport = false;
+
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
+ new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem));
+ mTestLooper.dispatchAll();
+
+ assertThat(mMsgRequestActiveSourceCount).isEqualTo(1);
+ assertThat(mMsgSetSystemAudioModeCount).isEqualTo(0);
+ assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(1);
+ assertFalse(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated());
+ }
+
+ @Test
+ public void testTvSupport() {
+ resetTestVariables();
+ mShouldDispatchActiveSource = true;
+ mTvSystemAudioModeSupport = true;
+
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
+ new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem));
+ mTestLooper.dispatchAll();
+
+ assertThat(mMsgRequestActiveSourceCount).isEqualTo(1);
+ assertThat(mMsgSetSystemAudioModeCount).isEqualTo(1);
+ assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(1);
+ assertTrue(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated());
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress).isEqualTo(1002);
+
+ }
+
+ @Test
+ public void testKnownActiveSource() {
+ resetTestVariables();
+ mTvSystemAudioModeSupport = true;
+ mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress = 1001;
+
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
+ new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem));
+ mTestLooper.dispatchAll();
+
+ assertThat(mMsgRequestActiveSourceCount).isEqualTo(0);
+ assertThat(mMsgSetSystemAudioModeCount).isEqualTo(1);
+ assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(1);
+ assertTrue(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated());
+ }
+
+ @Test
+ public void testRetry() {
+ resetTestVariables();
+ mTvSystemAudioModeSupport = true;
+ mShouldDispatchActiveSource = true;
+ mTryCountBeforeSucceed = 3;
+ assertThat(mTryCountBeforeSucceed)
+ .isAtMost(SystemAudioInitiationActionFromAvr.MAX_RETRY_COUNT);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress)
+ .isEqualTo(Constants.INVALID_PHYSICAL_ADDRESS);
+
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
+ new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem));
+ mTestLooper.dispatchAll();
+
+ assertThat(mMsgRequestActiveSourceCount).isEqualTo(4);
+ assertThat(mMsgSetSystemAudioModeCount).isEqualTo(4);
+ assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(1);
+ assertTrue(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated());
+ }
+
+ private void resetTestVariables() {
+ mMsgRequestActiveSourceCount = 0;
+ mMsgSetSystemAudioModeCount = 0;
+ mQueryTvSystemAudioModeSupportCount = 0;
+ mTryCountBeforeSucceed = 0;
+ mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress =
+ Constants.INVALID_PHYSICAL_ADDRESS;
+ }
+}