Loading services/core/java/com/android/server/hdmi/ActiveSourceHandler.java +1 −1 Original line number Diff line number Diff line Loading @@ -88,7 +88,7 @@ final class ActiveSourceHandler { tv.updateActiveSource(current, "ActiveSourceHandler"); invokeCallback(HdmiControlManager.RESULT_SUCCESS); } else { tv.startRoutingControl(newActive.physicalAddress, current.physicalAddress, true, tv.startRoutingControl(newActive.physicalAddress, current.physicalAddress, mCallback); } } Loading services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +7 −8 Original line number Diff line number Diff line Loading @@ -370,12 +370,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return; } int newPath = mService.portIdToPath(portId); startRoutingControl(oldPath, newPath, true, callback); startRoutingControl(oldPath, newPath, callback); } @ServiceThreadOnly void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus, IHdmiControlCallback callback) { void startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback) { assertRunOnServiceThread(); if (oldPath == newPath) { return; Loading @@ -385,7 +384,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.sendCecCommand(routingChange); removeAction(RoutingControlAction.class); addAndStartAction( new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback)); new RoutingControlAction(this, newPath, callback)); } @ServiceThreadOnly Loading Loading @@ -554,7 +553,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (isTailOfActivePath(path, getActivePath())) { int newPath = mService.portIdToPath(getActivePortId()); setActivePath(newPath); startRoutingControl(getActivePath(), newPath, false, null); startRoutingControl(getActivePath(), newPath, null); return true; } return false; Loading Loading @@ -598,7 +597,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { getActiveSource().invalidate(); removeAction(RoutingControlAction.class); int newPath = HdmiUtils.twoBytesToInt(params, 2); addAndStartAction(new RoutingControlAction(this, newPath, true, null)); addAndStartAction(new RoutingControlAction(this, newPath, null)); } return true; } Loading Loading @@ -1140,7 +1139,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Seq #23 if (isTailOfActivePath(path, getActivePath())) { int newPath = mService.portIdToPath(getActivePortId()); startRoutingControl(getActivePath(), newPath, true, null); startRoutingControl(getActivePath(), newPath, null); } } Loading @@ -1158,7 +1157,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (!routingForBootup && !isProhibitMode()) { int newPath = mService.portIdToPath(getActivePortId()); setActivePath(newPath); startRoutingControl(getActivePath(), newPath, routingForBootup, null); startRoutingControl(getActivePath(), newPath, null); } } else { int activePath = mService.getPhysicalAddress(); Loading services/core/java/com/android/server/hdmi/RoutingControlAction.java +9 −79 Original line number Diff line number Diff line Loading @@ -18,12 +18,11 @@ package com.android.server.hdmi; import android.annotation.Nullable; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.RemoteException; import android.util.Slog; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; import com.android.internal.annotations.VisibleForTesting; /** * Feature action for routing control. Exchanges routing-related commands with other devices Loading @@ -43,23 +42,12 @@ final class RoutingControlAction extends HdmiCecFeatureAction { // State in which we wait for <Routing Information> to arrive. If timed out, we use the // latest routing path to set the new active source. private static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1; // State in which we wait for <Report Power Status> in response to <Give Device Power Status> // we have sent. If the response tells us the device power is on, we send <Set Stream Path> // to make it the active source. Otherwise we do not send <Set Stream Path>, and possibly // just show the blank screen. private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 2; @VisibleForTesting static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1; // Time out in millseconds used for <Routing Information> private static final int TIMEOUT_ROUTING_INFORMATION_MS = 1000; // Time out in milliseconds used for <Report Power Status> private static final int TIMEOUT_REPORT_POWER_STATUS_MS = 1000; // true if <Give Power Status> should be sent once the new active routing path is determined. private final boolean mQueryDevicePowerStatus; // If set to true, call {@link HdmiControlService#invokeInputChangeListener()} when // the routing control/active source change happens. The listener should be called if // the events are triggered by external events such as manual switch port change or incoming Loading @@ -71,12 +59,10 @@ final class RoutingControlAction extends HdmiCecFeatureAction { // The latest routing path. Updated by each <Routing Information> from CEC switches. private int mCurrentRoutingPath; RoutingControlAction(HdmiCecLocalDevice localDevice, int path, boolean queryDevicePowerStatus, IHdmiControlCallback callback) { RoutingControlAction(HdmiCecLocalDevice localDevice, int path, IHdmiControlCallback callback) { super(localDevice); mCallback = callback; mCurrentRoutingPath = path; mQueryDevicePowerStatus = queryDevicePowerStatus; // Callback is non-null when routing control action is brought up by binder API. Use // this as an indicator for the input change notification. These API calls will get // the result through this callback, not through notification. Any other events that Loading Loading @@ -109,39 +95,16 @@ final class RoutingControlAction extends HdmiCecFeatureAction { removeActionExcept(RoutingControlAction.class, this); addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); return true; } else if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS && opcode == Constants.MESSAGE_REPORT_POWER_STATUS) { handleReportPowerStatus(cmd.getParams()[0]); return true; } return false; } private void handleReportPowerStatus(int devicePowerStatus) { if (isPowerOnOrTransient(getTvPowerStatus())) { updateActiveInput(); if (isPowerOnOrTransient(devicePowerStatus)) { sendSetStreamPath(); } } finishWithCallback(HdmiControlManager.RESULT_SUCCESS); } private void updateActiveInput() { HdmiCecLocalDeviceTv tv = tv(); tv.setPrevPortId(tv.getActivePortId()); tv.updateActiveInput(mCurrentRoutingPath, mNotifyInputChange); } private int getTvPowerStatus() { return tv().getPowerStatus(); } private static boolean isPowerOnOrTransient(int status) { return status == HdmiControlManager.POWER_STATUS_ON || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; } private void sendSetStreamPath() { sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(), mCurrentRoutingPath)); Loading @@ -160,46 +123,13 @@ final class RoutingControlAction extends HdmiCecFeatureAction { } switch (timeoutState) { case STATE_WAIT_FOR_ROUTING_INFORMATION: HdmiDeviceInfo device = localDevice().mService.getHdmiCecNetwork().getDeviceInfoByPath( mCurrentRoutingPath); if (device != null && mQueryDevicePowerStatus) { int deviceLogicalAddress = device.getLogicalAddress(); queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() { @Override public void onSendCompleted(int error) { handlDevicePowerStatusAckResult( error == HdmiControlManager.RESULT_SUCCESS); } }); } else { updateActiveInput(); finishWithCallback(HdmiControlManager.RESULT_SUCCESS); } return; case STATE_WAIT_FOR_REPORT_POWER_STATUS: if (isPowerOnOrTransient(getTvPowerStatus())) { updateActiveInput(); sendSetStreamPath(); } finishWithCallback(HdmiControlManager.RESULT_SUCCESS); return; } } private void queryDevicePowerStatus(int address, SendMessageCallback callback) { sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address), callback); } private void handlDevicePowerStatusAckResult(boolean acked) { if (acked) { mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS); } else { updateActiveInput(); sendSetStreamPath(); finishWithCallback(HdmiControlManager.RESULT_SUCCESS); default: Slog.e("CEC", "Invalid timeoutState (" + timeoutState + ")."); return; } } Loading services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java 0 → 100644 +280 −0 Original line number Diff line number Diff line /* * Copyright (C) 2014 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.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_TUNER_1; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; import static com.android.server.hdmi.Constants.MESSAGE_ACTIVE_SOURCE; import static com.android.server.hdmi.Constants.MESSAGE_ROUTING_INFORMATION; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.RoutingControlAction.STATE_WAIT_FOR_ROUTING_INFORMATION; import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.Handler; import android.os.IPowerManager; import android.os.IThermalService; import android.os.Looper; import android.os.PowerManager; import android.os.test.TestLooper; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; @SmallTest @RunWith(JUnit4.class) public class RoutingControlActionTest { /* * Example connection diagram used in tests. Double-lined paths indicate the currently active * routes. * * * +-----------+ * | TV | * | 0.0.0.0 | * +---+-----+-+ * | | * <----------+ 1) AVR -> Switch * +----------+ | | +-----------+ * | AVR +---------+ +--+ Switch | * | 1.0.0.0 | | 2.0.0.0 | * +--+---++--+ +--++-----+-+ <-------+ 2) Recorder -> Blu-ray * | || || | * | || || +--------+ * +-----------+ | || +----------+ +----++----+ | * | XBox +--+ ++--+ Tuner | | Blueray | +-----+----+ * | 1.1.0.0 | | 1.2.0.0 | | 2.1.0.0 | | Recorder | * +-----------+ +----++----+ +----------+ | 2.2.0.0 | * || +----------+ * || * +----++----+ * | Player | * | 1.2.1.0 | * +----------+ * */ private static final int PHYSICAL_ADDRESS_TV = 0x0000; private static final int PHYSICAL_ADDRESS_AVR = 0x1000; private static final int PHYSICAL_ADDRESS_SWITCH = 0x2000; private static final int PHYSICAL_ADDRESS_TUNER = 0x1200; private static final int PHYSICAL_ADDRESS_PLAYER = 0x1210; private static final int PHYSICAL_ADDRESS_BLUERAY = 0x2100; private static final int PHYSICAL_ADDRESS_RECORDER = 0x2200; private static final int PORT_1 = 1; private static final int PORT_2 = 2; private static final int VENDOR_ID_AVR = 0x11233; private static final byte[] TUNER_PARAM = new byte[] {(PHYSICAL_ADDRESS_TUNER >> 8) & 0xFF, PHYSICAL_ADDRESS_TUNER & 0xFF}; private static final byte[] PLAYER_PARAM = new byte[] {(PHYSICAL_ADDRESS_PLAYER >> 8) & 0xFF, PHYSICAL_ADDRESS_PLAYER & 0xFF}; private static final HdmiDeviceInfo DEVICE_INFO_AVR = new HdmiDeviceInfo(ADDR_AUDIO_SYSTEM, PHYSICAL_ADDRESS_AVR, PORT_1, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, VENDOR_ID_AVR, "Audio"); private static final HdmiDeviceInfo DEVICE_INFO_PLAYER = new HdmiDeviceInfo(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYER, PORT_1, HdmiDeviceInfo.DEVICE_PLAYBACK, VENDOR_ID_AVR, "Player"); private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = new HdmiCecMessage( ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, TUNER_PARAM); private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = new HdmiCecMessage( ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, PLAYER_PARAM); private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = new HdmiCecMessage( ADDR_TUNER_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, TUNER_PARAM); private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = new HdmiCecMessage( ADDR_PLAYBACK_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, PLAYER_PARAM); private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv; private FakeNativeWrapper mNativeWrapper; private Looper mMyLooper; private TestLooper mTestLooper = new TestLooper(); private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); @Mock private IPowerManager mIPowerManagerMock; @Mock private IThermalService mIThermalServiceMock; @Before public void setUp() { MockitoAnnotations.initMocks(this); Context context = InstrumentationRegistry.getTargetContext(); mMyLooper = mTestLooper.getLooper(); PowerManager powerManager = new PowerManager(context, mIPowerManagerMock, mIThermalServiceMock, new Handler(mMyLooper)); HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context); mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override boolean isControlEnabled() { return true; } @Override void wakeUp() { } @Override protected void writeStringSystemProperty(String key, String value) { // do nothing } @Override boolean isPowerStandbyOrTransient() { return false; } @Override protected PowerManager getPowerManager() { return powerManager; } @Override protected HdmiCecConfig getHdmiCecConfig() { return hdmiCecConfig; } }; mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService); mHdmiCecLocalDeviceTv.init(); mHdmiControlService.setIoLooper(mMyLooper); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_AVR, true, false, false); mNativeWrapper.setPortInfo(hdmiPortInfos); mHdmiControlService.initService(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mNativeWrapper.setPhysicalAddress(0x0000); mTestLooper.dispatchAll(); mNativeWrapper.clearResultMessages(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR); } private static class TestActionTimer implements ActionTimer { private int mState; @Override public void sendTimerMessage(int state, long delayMillis) { mState = state; } @Override public void clearTimerMessage() { } private int getState() { return mState; } } private static class TestInputSelectCallback extends IHdmiControlCallback.Stub { private final List<Integer> mCallbackResult = new ArrayList<Integer>(); @Override public void onComplete(int result) { mCallbackResult.add(result); } private int getResult() { assert (mCallbackResult.size() == 1); return mCallbackResult.get(0); } } private static RoutingControlAction createRoutingControlAction(HdmiCecLocalDeviceTv localDevice, TestInputSelectCallback callback) { return new RoutingControlAction(localDevice, PHYSICAL_ADDRESS_AVR, callback); } // Routing control succeeds against the device connected directly to the port. Action // won't get any <Routing Information> in this case. It times out on <Routing Information>, // regards the directly connected one as the new routing path to switch to. @Test public void testRoutingControl_succeedForDirectlyConnectedDevice() { TestInputSelectCallback callback = new TestInputSelectCallback(); TestActionTimer actionTimer = new TestActionTimer(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR); RoutingControlAction action = createRoutingControlAction(mHdmiCecLocalDeviceTv, callback); action.setActionTimer(actionTimer); action.start(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_ROUTING_INFORMATION); action.handleTimerEvent(actionTimer.getState()); mTestLooper.dispatchAll(); HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath( ADDR_TV, PHYSICAL_ADDRESS_AVR); assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath); } // Succeeds by receiving a couple of <Routing Information> commands, followed by // <Set Stream Path> going out in the end. @Test public void testRoutingControl_succeedForDeviceBehindSwitch() { TestInputSelectCallback callback = new TestInputSelectCallback(); TestActionTimer actionTimer = new TestActionTimer(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_PLAYER); RoutingControlAction action = createRoutingControlAction(mHdmiCecLocalDeviceTv, callback); action.setActionTimer(actionTimer); action.start(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_ROUTING_INFORMATION); action.processCommand(ROUTING_INFORMATION_TUNER); action.processCommand(ROUTING_INFORMATION_PLAYER); action.handleTimerEvent(actionTimer.getState()); mTestLooper.dispatchAll(); HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath( ADDR_TV, PHYSICAL_ADDRESS_PLAYER); assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath); } } Loading
services/core/java/com/android/server/hdmi/ActiveSourceHandler.java +1 −1 Original line number Diff line number Diff line Loading @@ -88,7 +88,7 @@ final class ActiveSourceHandler { tv.updateActiveSource(current, "ActiveSourceHandler"); invokeCallback(HdmiControlManager.RESULT_SUCCESS); } else { tv.startRoutingControl(newActive.physicalAddress, current.physicalAddress, true, tv.startRoutingControl(newActive.physicalAddress, current.physicalAddress, mCallback); } } Loading
services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +7 −8 Original line number Diff line number Diff line Loading @@ -370,12 +370,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return; } int newPath = mService.portIdToPath(portId); startRoutingControl(oldPath, newPath, true, callback); startRoutingControl(oldPath, newPath, callback); } @ServiceThreadOnly void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus, IHdmiControlCallback callback) { void startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback) { assertRunOnServiceThread(); if (oldPath == newPath) { return; Loading @@ -385,7 +384,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.sendCecCommand(routingChange); removeAction(RoutingControlAction.class); addAndStartAction( new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback)); new RoutingControlAction(this, newPath, callback)); } @ServiceThreadOnly Loading Loading @@ -554,7 +553,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (isTailOfActivePath(path, getActivePath())) { int newPath = mService.portIdToPath(getActivePortId()); setActivePath(newPath); startRoutingControl(getActivePath(), newPath, false, null); startRoutingControl(getActivePath(), newPath, null); return true; } return false; Loading Loading @@ -598,7 +597,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { getActiveSource().invalidate(); removeAction(RoutingControlAction.class); int newPath = HdmiUtils.twoBytesToInt(params, 2); addAndStartAction(new RoutingControlAction(this, newPath, true, null)); addAndStartAction(new RoutingControlAction(this, newPath, null)); } return true; } Loading Loading @@ -1140,7 +1139,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Seq #23 if (isTailOfActivePath(path, getActivePath())) { int newPath = mService.portIdToPath(getActivePortId()); startRoutingControl(getActivePath(), newPath, true, null); startRoutingControl(getActivePath(), newPath, null); } } Loading @@ -1158,7 +1157,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (!routingForBootup && !isProhibitMode()) { int newPath = mService.portIdToPath(getActivePortId()); setActivePath(newPath); startRoutingControl(getActivePath(), newPath, routingForBootup, null); startRoutingControl(getActivePath(), newPath, null); } } else { int activePath = mService.getPhysicalAddress(); Loading
services/core/java/com/android/server/hdmi/RoutingControlAction.java +9 −79 Original line number Diff line number Diff line Loading @@ -18,12 +18,11 @@ package com.android.server.hdmi; import android.annotation.Nullable; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.RemoteException; import android.util.Slog; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; import com.android.internal.annotations.VisibleForTesting; /** * Feature action for routing control. Exchanges routing-related commands with other devices Loading @@ -43,23 +42,12 @@ final class RoutingControlAction extends HdmiCecFeatureAction { // State in which we wait for <Routing Information> to arrive. If timed out, we use the // latest routing path to set the new active source. private static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1; // State in which we wait for <Report Power Status> in response to <Give Device Power Status> // we have sent. If the response tells us the device power is on, we send <Set Stream Path> // to make it the active source. Otherwise we do not send <Set Stream Path>, and possibly // just show the blank screen. private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 2; @VisibleForTesting static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1; // Time out in millseconds used for <Routing Information> private static final int TIMEOUT_ROUTING_INFORMATION_MS = 1000; // Time out in milliseconds used for <Report Power Status> private static final int TIMEOUT_REPORT_POWER_STATUS_MS = 1000; // true if <Give Power Status> should be sent once the new active routing path is determined. private final boolean mQueryDevicePowerStatus; // If set to true, call {@link HdmiControlService#invokeInputChangeListener()} when // the routing control/active source change happens. The listener should be called if // the events are triggered by external events such as manual switch port change or incoming Loading @@ -71,12 +59,10 @@ final class RoutingControlAction extends HdmiCecFeatureAction { // The latest routing path. Updated by each <Routing Information> from CEC switches. private int mCurrentRoutingPath; RoutingControlAction(HdmiCecLocalDevice localDevice, int path, boolean queryDevicePowerStatus, IHdmiControlCallback callback) { RoutingControlAction(HdmiCecLocalDevice localDevice, int path, IHdmiControlCallback callback) { super(localDevice); mCallback = callback; mCurrentRoutingPath = path; mQueryDevicePowerStatus = queryDevicePowerStatus; // Callback is non-null when routing control action is brought up by binder API. Use // this as an indicator for the input change notification. These API calls will get // the result through this callback, not through notification. Any other events that Loading Loading @@ -109,39 +95,16 @@ final class RoutingControlAction extends HdmiCecFeatureAction { removeActionExcept(RoutingControlAction.class, this); addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); return true; } else if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS && opcode == Constants.MESSAGE_REPORT_POWER_STATUS) { handleReportPowerStatus(cmd.getParams()[0]); return true; } return false; } private void handleReportPowerStatus(int devicePowerStatus) { if (isPowerOnOrTransient(getTvPowerStatus())) { updateActiveInput(); if (isPowerOnOrTransient(devicePowerStatus)) { sendSetStreamPath(); } } finishWithCallback(HdmiControlManager.RESULT_SUCCESS); } private void updateActiveInput() { HdmiCecLocalDeviceTv tv = tv(); tv.setPrevPortId(tv.getActivePortId()); tv.updateActiveInput(mCurrentRoutingPath, mNotifyInputChange); } private int getTvPowerStatus() { return tv().getPowerStatus(); } private static boolean isPowerOnOrTransient(int status) { return status == HdmiControlManager.POWER_STATUS_ON || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; } private void sendSetStreamPath() { sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(), mCurrentRoutingPath)); Loading @@ -160,46 +123,13 @@ final class RoutingControlAction extends HdmiCecFeatureAction { } switch (timeoutState) { case STATE_WAIT_FOR_ROUTING_INFORMATION: HdmiDeviceInfo device = localDevice().mService.getHdmiCecNetwork().getDeviceInfoByPath( mCurrentRoutingPath); if (device != null && mQueryDevicePowerStatus) { int deviceLogicalAddress = device.getLogicalAddress(); queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() { @Override public void onSendCompleted(int error) { handlDevicePowerStatusAckResult( error == HdmiControlManager.RESULT_SUCCESS); } }); } else { updateActiveInput(); finishWithCallback(HdmiControlManager.RESULT_SUCCESS); } return; case STATE_WAIT_FOR_REPORT_POWER_STATUS: if (isPowerOnOrTransient(getTvPowerStatus())) { updateActiveInput(); sendSetStreamPath(); } finishWithCallback(HdmiControlManager.RESULT_SUCCESS); return; } } private void queryDevicePowerStatus(int address, SendMessageCallback callback) { sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address), callback); } private void handlDevicePowerStatusAckResult(boolean acked) { if (acked) { mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS); } else { updateActiveInput(); sendSetStreamPath(); finishWithCallback(HdmiControlManager.RESULT_SUCCESS); default: Slog.e("CEC", "Invalid timeoutState (" + timeoutState + ")."); return; } } Loading
services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java 0 → 100644 +280 −0 Original line number Diff line number Diff line /* * Copyright (C) 2014 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.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_TUNER_1; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; import static com.android.server.hdmi.Constants.MESSAGE_ACTIVE_SOURCE; import static com.android.server.hdmi.Constants.MESSAGE_ROUTING_INFORMATION; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.RoutingControlAction.STATE_WAIT_FOR_ROUTING_INFORMATION; import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.Handler; import android.os.IPowerManager; import android.os.IThermalService; import android.os.Looper; import android.os.PowerManager; import android.os.test.TestLooper; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; @SmallTest @RunWith(JUnit4.class) public class RoutingControlActionTest { /* * Example connection diagram used in tests. Double-lined paths indicate the currently active * routes. * * * +-----------+ * | TV | * | 0.0.0.0 | * +---+-----+-+ * | | * <----------+ 1) AVR -> Switch * +----------+ | | +-----------+ * | AVR +---------+ +--+ Switch | * | 1.0.0.0 | | 2.0.0.0 | * +--+---++--+ +--++-----+-+ <-------+ 2) Recorder -> Blu-ray * | || || | * | || || +--------+ * +-----------+ | || +----------+ +----++----+ | * | XBox +--+ ++--+ Tuner | | Blueray | +-----+----+ * | 1.1.0.0 | | 1.2.0.0 | | 2.1.0.0 | | Recorder | * +-----------+ +----++----+ +----------+ | 2.2.0.0 | * || +----------+ * || * +----++----+ * | Player | * | 1.2.1.0 | * +----------+ * */ private static final int PHYSICAL_ADDRESS_TV = 0x0000; private static final int PHYSICAL_ADDRESS_AVR = 0x1000; private static final int PHYSICAL_ADDRESS_SWITCH = 0x2000; private static final int PHYSICAL_ADDRESS_TUNER = 0x1200; private static final int PHYSICAL_ADDRESS_PLAYER = 0x1210; private static final int PHYSICAL_ADDRESS_BLUERAY = 0x2100; private static final int PHYSICAL_ADDRESS_RECORDER = 0x2200; private static final int PORT_1 = 1; private static final int PORT_2 = 2; private static final int VENDOR_ID_AVR = 0x11233; private static final byte[] TUNER_PARAM = new byte[] {(PHYSICAL_ADDRESS_TUNER >> 8) & 0xFF, PHYSICAL_ADDRESS_TUNER & 0xFF}; private static final byte[] PLAYER_PARAM = new byte[] {(PHYSICAL_ADDRESS_PLAYER >> 8) & 0xFF, PHYSICAL_ADDRESS_PLAYER & 0xFF}; private static final HdmiDeviceInfo DEVICE_INFO_AVR = new HdmiDeviceInfo(ADDR_AUDIO_SYSTEM, PHYSICAL_ADDRESS_AVR, PORT_1, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, VENDOR_ID_AVR, "Audio"); private static final HdmiDeviceInfo DEVICE_INFO_PLAYER = new HdmiDeviceInfo(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYER, PORT_1, HdmiDeviceInfo.DEVICE_PLAYBACK, VENDOR_ID_AVR, "Player"); private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = new HdmiCecMessage( ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, TUNER_PARAM); private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = new HdmiCecMessage( ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, PLAYER_PARAM); private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = new HdmiCecMessage( ADDR_TUNER_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, TUNER_PARAM); private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = new HdmiCecMessage( ADDR_PLAYBACK_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, PLAYER_PARAM); private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv; private FakeNativeWrapper mNativeWrapper; private Looper mMyLooper; private TestLooper mTestLooper = new TestLooper(); private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); @Mock private IPowerManager mIPowerManagerMock; @Mock private IThermalService mIThermalServiceMock; @Before public void setUp() { MockitoAnnotations.initMocks(this); Context context = InstrumentationRegistry.getTargetContext(); mMyLooper = mTestLooper.getLooper(); PowerManager powerManager = new PowerManager(context, mIPowerManagerMock, mIThermalServiceMock, new Handler(mMyLooper)); HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context); mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override boolean isControlEnabled() { return true; } @Override void wakeUp() { } @Override protected void writeStringSystemProperty(String key, String value) { // do nothing } @Override boolean isPowerStandbyOrTransient() { return false; } @Override protected PowerManager getPowerManager() { return powerManager; } @Override protected HdmiCecConfig getHdmiCecConfig() { return hdmiCecConfig; } }; mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService); mHdmiCecLocalDeviceTv.init(); mHdmiControlService.setIoLooper(mMyLooper); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_AVR, true, false, false); mNativeWrapper.setPortInfo(hdmiPortInfos); mHdmiControlService.initService(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mNativeWrapper.setPhysicalAddress(0x0000); mTestLooper.dispatchAll(); mNativeWrapper.clearResultMessages(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR); } private static class TestActionTimer implements ActionTimer { private int mState; @Override public void sendTimerMessage(int state, long delayMillis) { mState = state; } @Override public void clearTimerMessage() { } private int getState() { return mState; } } private static class TestInputSelectCallback extends IHdmiControlCallback.Stub { private final List<Integer> mCallbackResult = new ArrayList<Integer>(); @Override public void onComplete(int result) { mCallbackResult.add(result); } private int getResult() { assert (mCallbackResult.size() == 1); return mCallbackResult.get(0); } } private static RoutingControlAction createRoutingControlAction(HdmiCecLocalDeviceTv localDevice, TestInputSelectCallback callback) { return new RoutingControlAction(localDevice, PHYSICAL_ADDRESS_AVR, callback); } // Routing control succeeds against the device connected directly to the port. Action // won't get any <Routing Information> in this case. It times out on <Routing Information>, // regards the directly connected one as the new routing path to switch to. @Test public void testRoutingControl_succeedForDirectlyConnectedDevice() { TestInputSelectCallback callback = new TestInputSelectCallback(); TestActionTimer actionTimer = new TestActionTimer(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR); RoutingControlAction action = createRoutingControlAction(mHdmiCecLocalDeviceTv, callback); action.setActionTimer(actionTimer); action.start(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_ROUTING_INFORMATION); action.handleTimerEvent(actionTimer.getState()); mTestLooper.dispatchAll(); HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath( ADDR_TV, PHYSICAL_ADDRESS_AVR); assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath); } // Succeeds by receiving a couple of <Routing Information> commands, followed by // <Set Stream Path> going out in the end. @Test public void testRoutingControl_succeedForDeviceBehindSwitch() { TestInputSelectCallback callback = new TestInputSelectCallback(); TestActionTimer actionTimer = new TestActionTimer(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_PLAYER); RoutingControlAction action = createRoutingControlAction(mHdmiCecLocalDeviceTv, callback); action.setActionTimer(actionTimer); action.start(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_ROUTING_INFORMATION); action.processCommand(ROUTING_INFORMATION_TUNER); action.processCommand(ROUTING_INFORMATION_PLAYER); action.handleTimerEvent(actionTimer.getState()); mTestLooper.dispatchAll(); HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath( ADDR_TV, PHYSICAL_ADDRESS_PLAYER); assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath); } }