From 9582aceaba0bd738afe6aaf240fdfca2f71a403b Mon Sep 17 00:00:00 2001 From: Michal Olech Date: Fri, 8 May 2020 14:58:16 +0200 Subject: [PATCH] [CEC] Always send after Routing Changes Completely removes querying for power status of the tail device Bug: 154458501 Test: atest RoutingControlActionTest Change-Id: Ief7d7d5d72e6c8f17889832469d82e1fdf9115ad --- .../server/hdmi/ActiveSourceHandler.java | 2 +- .../server/hdmi/HdmiCecLocalDeviceTv.java | 15 +- .../server/hdmi/RoutingControlAction.java | 88 +----- .../server/hdmi/RoutingControlActionTest.java | 280 ++++++++++++++++++ 4 files changed, 297 insertions(+), 88 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java index 8405bbe38b128..d422d51d4087b 100644 --- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java +++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java @@ -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); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index a3e18d1617519..2da8835b2c3d1 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -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; @@ -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 @@ -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; @@ -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; } @@ -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); } } @@ -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(); diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java index 6c147ed5e6d6f..5edd35abf7d10 100644 --- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java +++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java @@ -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 @@ -43,23 +42,12 @@ final class RoutingControlAction extends HdmiCecFeatureAction { // State in which we wait for 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 in response to - // we have sent. If the response tells us the device power is on, we send - // to make it the active source. Otherwise we do not send , 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 private static final int TIMEOUT_ROUTING_INFORMATION_MS = 1000; - // Time out in milliseconds used for - private static final int TIMEOUT_REPORT_POWER_STATUS_MS = 1000; - - // true if 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 @@ -71,12 +59,10 @@ final class RoutingControlAction extends HdmiCecFeatureAction { // The latest routing path. Updated by each 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 @@ -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)); @@ -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(); - } + 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; } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java new file mode 100644 index 0000000000000..0ed6d7bec010d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java @@ -0,0 +1,280 @@ +/* + * 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 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 mCallbackResult = new ArrayList(); + + @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 in this case. It times out on , + // 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 commands, followed by + // 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); + } +}