Loading services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +32 −3 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback; import android.hardware.hdmi.IHdmiControlCallback; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; /** * Feature action that performs one touch play against TV/Display device. This action is initiated Loading @@ -40,13 +41,15 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { // standby mode, and do not accept the command until their power status becomes 'ON'. // For a workaround, we send <Give Device Power Status> commands periodically to make sure // the device switches its status to 'ON'. Then we send additional <Active Source>. private static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1; @VisibleForTesting static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1; // The maximum number of times we send <Give Device Power Status> before we give up. // We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds. private static final int LOOP_COUNTER_MAX = 10; private final int mTargetAddress; private final boolean mIsCec20; private int mPowerStatusCounter = 0; Loading @@ -65,8 +68,19 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { private OneTouchPlayAction(HdmiCecLocalDevice localDevice, int targetAddress, IHdmiControlCallback callback) { this(localDevice, targetAddress, callback, localDevice.getDeviceInfo().getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0 && localDevice.mService.getHdmiCecNetwork().getCecDeviceInfo( targetAddress).getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0); } @VisibleForTesting OneTouchPlayAction(HdmiCecLocalDevice localDevice, int targetAddress, IHdmiControlCallback callback, boolean isCec20) { super(localDevice, callback); mTargetAddress = targetAddress; mIsCec20 = isCec20; } @Override Loading @@ -74,6 +88,9 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { // Because only source device can create this action, it's safe to cast. mSource = source(); sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress)); boolean targetOnBefore = localDevice().mService.getHdmiCecNetwork() .getCecDeviceInfo(mTargetAddress).getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON; broadcastActiveSource(); // If the device is not an audio system itself, request the connected audio system to // turn on. Loading @@ -81,7 +98,20 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { sendCommand(HdmiCecMessageBuilder.buildSystemAudioModeRequest(getSourceAddress(), Constants.ADDR_AUDIO_SYSTEM, getSourcePath(), true)); } int targetPowerStatus = localDevice().mService.getHdmiCecNetwork() .getCecDeviceInfo(mTargetAddress).getDevicePowerStatus(); if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) { queryDevicePowerStatus(); } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) { if (!targetOnBefore) { // Suppress 2nd <Active Source> message if the target device was already on when // the 1st one was sent. broadcastActiveSource(); } finishWithCallback(HdmiControlManager.RESULT_SUCCESS); return true; } mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; addTimer(mState, HdmiConfig.TIMEOUT_MS); return true; } Loading @@ -101,7 +131,6 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { } private void queryDevicePowerStatus() { mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), mTargetAddress)); } Loading services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +7 −0 Original line number Diff line number Diff line Loading @@ -61,6 +61,12 @@ import java.util.concurrent.TimeUnit; /** Tests for {@link HdmiCecLocalDevicePlayback} class. */ public class HdmiCecLocalDevicePlaybackTest { private static final int PORT_1 = 1; private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo( ADDR_TV, 0x0000, PORT_1, HdmiDeviceInfo.DEVICE_TV, 0x1234, "TV", HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback; Loading Loading @@ -159,6 +165,7 @@ public class HdmiCecLocalDevicePlaybackTest { mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress); mTestLooper.dispatchAll(); mPlaybackLogicalAddress = mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); mNativeWrapper.clearResultMessages(); } Loading services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java 0 → 100644 +420 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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_TV; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; import android.content.ContextWrapper; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.media.AudioManager; 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; /** Tests for {@link OneTouchPlayAction} */ @SmallTest @RunWith(JUnit4.class) public class OneTouchPlayActionTest { private static final byte[] POWER_ON = new byte[]{HdmiControlManager.POWER_STATUS_ON}; private static final byte[] POWER_TRANSIENT_TO_ON = new byte[]{HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON}; private static final int PORT_1 = 1; private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo( ADDR_TV, 0x0000, PORT_1, HdmiDeviceInfo.DEVICE_TV, 0x1234, "TV", HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); private Context mContextSpy; private HdmiControlService mHdmiControlService; private FakeNativeWrapper mNativeWrapper; private TestLooper mTestLooper = new TestLooper(); private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); private int mPhysicalAddress; @Mock private IPowerManager mIPowerManagerMock; @Mock private IThermalService mIThermalServiceMock; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, mIThermalServiceMock, new Handler(mTestLooper.getLooper())); when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); mHdmiControlService = new HdmiControlService(mContextSpy) { @Override AudioManager getAudioManager() { return new AudioManager() { @Override public void setWiredDeviceConnectionState( int type, int state, String address, String name) { // Do nothing. } }; } @Override void wakeUp() { } @Override boolean isPowerStandby() { return false; } @Override protected PowerManager getPowerManager() { return powerManager; } @Override protected void writeStringSystemProperty(String key, String value) { // do nothing } }; Looper looper = mTestLooper.getLooper(); mHdmiControlService.setIoLooper(looper); mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); mNativeWrapper = new FakeNativeWrapper(); HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiControlService.initService(); mPhysicalAddress = 0x2000; mNativeWrapper.setPhysicalAddress(mPhysicalAddress); mTestLooper.dispatchAll(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); } private OneTouchPlayAction createOneTouchPlayAction(HdmiCecLocalDevicePlayback device, TestActionTimer actionTimer, TestCallback callback, boolean isCec20) { OneTouchPlayAction action = new OneTouchPlayAction(device, ADDR_TV, callback, isCec20); action.setActionTimer(actionTimer); return action; } @Test public void succeedAfterGettingPowerStatusOn_Cec14b() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, false); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = new HdmiCecMessage( ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); action.processCommand(reportPowerStatusOn); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @Test public void succeedAfterGettingTransientPowerStatus_Cec14b() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, false); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusTransientToOn = new HdmiCecMessage( ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON); action.processCommand(reportPowerStatusTransientToOn); action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); HdmiCecMessage reportPowerStatusOn = new HdmiCecMessage( ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); action.processCommand(reportPowerStatusOn); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @Test public void timeOut_Cec14b() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, false); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); for (int i = 0; i < 10; ++i) { action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS); mTestLooper.dispatchAll(); } assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT); } @Test public void succeedIfPowerStatusOn_Cec20() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV, HdmiControlManager.POWER_STATUS_ON); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, true); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @Test public void succeedIfPowerStatusUnknown_Cec20() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV, HdmiControlManager.POWER_STATUS_UNKNOWN); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, true); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = new HdmiCecMessage( ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); action.processCommand(reportPowerStatusOn); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @Test public void succeedIfPowerStatusStandby_Cec20() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV, HdmiControlManager.POWER_STATUS_STANDBY); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, true); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = new HdmiCecMessage( ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); action.processCommand(reportPowerStatusOn); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } 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 TestCallback extends IHdmiControlCallback.Stub { private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>(); @Override public void onComplete(int result) { mCallbackResult.add(result); } private int getResult() { assertThat(mCallbackResult.size()).isEqualTo(1); return mCallbackResult.get(0); } } } Loading
services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +32 −3 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback; import android.hardware.hdmi.IHdmiControlCallback; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; /** * Feature action that performs one touch play against TV/Display device. This action is initiated Loading @@ -40,13 +41,15 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { // standby mode, and do not accept the command until their power status becomes 'ON'. // For a workaround, we send <Give Device Power Status> commands periodically to make sure // the device switches its status to 'ON'. Then we send additional <Active Source>. private static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1; @VisibleForTesting static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1; // The maximum number of times we send <Give Device Power Status> before we give up. // We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds. private static final int LOOP_COUNTER_MAX = 10; private final int mTargetAddress; private final boolean mIsCec20; private int mPowerStatusCounter = 0; Loading @@ -65,8 +68,19 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { private OneTouchPlayAction(HdmiCecLocalDevice localDevice, int targetAddress, IHdmiControlCallback callback) { this(localDevice, targetAddress, callback, localDevice.getDeviceInfo().getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0 && localDevice.mService.getHdmiCecNetwork().getCecDeviceInfo( targetAddress).getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0); } @VisibleForTesting OneTouchPlayAction(HdmiCecLocalDevice localDevice, int targetAddress, IHdmiControlCallback callback, boolean isCec20) { super(localDevice, callback); mTargetAddress = targetAddress; mIsCec20 = isCec20; } @Override Loading @@ -74,6 +88,9 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { // Because only source device can create this action, it's safe to cast. mSource = source(); sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress)); boolean targetOnBefore = localDevice().mService.getHdmiCecNetwork() .getCecDeviceInfo(mTargetAddress).getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON; broadcastActiveSource(); // If the device is not an audio system itself, request the connected audio system to // turn on. Loading @@ -81,7 +98,20 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { sendCommand(HdmiCecMessageBuilder.buildSystemAudioModeRequest(getSourceAddress(), Constants.ADDR_AUDIO_SYSTEM, getSourcePath(), true)); } int targetPowerStatus = localDevice().mService.getHdmiCecNetwork() .getCecDeviceInfo(mTargetAddress).getDevicePowerStatus(); if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) { queryDevicePowerStatus(); } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) { if (!targetOnBefore) { // Suppress 2nd <Active Source> message if the target device was already on when // the 1st one was sent. broadcastActiveSource(); } finishWithCallback(HdmiControlManager.RESULT_SUCCESS); return true; } mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; addTimer(mState, HdmiConfig.TIMEOUT_MS); return true; } Loading @@ -101,7 +131,6 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { } private void queryDevicePowerStatus() { mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), mTargetAddress)); } Loading
services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +7 −0 Original line number Diff line number Diff line Loading @@ -61,6 +61,12 @@ import java.util.concurrent.TimeUnit; /** Tests for {@link HdmiCecLocalDevicePlayback} class. */ public class HdmiCecLocalDevicePlaybackTest { private static final int PORT_1 = 1; private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo( ADDR_TV, 0x0000, PORT_1, HdmiDeviceInfo.DEVICE_TV, 0x1234, "TV", HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback; Loading Loading @@ -159,6 +165,7 @@ public class HdmiCecLocalDevicePlaybackTest { mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress); mTestLooper.dispatchAll(); mPlaybackLogicalAddress = mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); mNativeWrapper.clearResultMessages(); } Loading
services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java 0 → 100644 +420 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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_TV; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; import android.content.ContextWrapper; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.media.AudioManager; 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; /** Tests for {@link OneTouchPlayAction} */ @SmallTest @RunWith(JUnit4.class) public class OneTouchPlayActionTest { private static final byte[] POWER_ON = new byte[]{HdmiControlManager.POWER_STATUS_ON}; private static final byte[] POWER_TRANSIENT_TO_ON = new byte[]{HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON}; private static final int PORT_1 = 1; private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo( ADDR_TV, 0x0000, PORT_1, HdmiDeviceInfo.DEVICE_TV, 0x1234, "TV", HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); private Context mContextSpy; private HdmiControlService mHdmiControlService; private FakeNativeWrapper mNativeWrapper; private TestLooper mTestLooper = new TestLooper(); private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); private int mPhysicalAddress; @Mock private IPowerManager mIPowerManagerMock; @Mock private IThermalService mIThermalServiceMock; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, mIThermalServiceMock, new Handler(mTestLooper.getLooper())); when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); mHdmiControlService = new HdmiControlService(mContextSpy) { @Override AudioManager getAudioManager() { return new AudioManager() { @Override public void setWiredDeviceConnectionState( int type, int state, String address, String name) { // Do nothing. } }; } @Override void wakeUp() { } @Override boolean isPowerStandby() { return false; } @Override protected PowerManager getPowerManager() { return powerManager; } @Override protected void writeStringSystemProperty(String key, String value) { // do nothing } }; Looper looper = mTestLooper.getLooper(); mHdmiControlService.setIoLooper(looper); mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); mNativeWrapper = new FakeNativeWrapper(); HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiControlService.initService(); mPhysicalAddress = 0x2000; mNativeWrapper.setPhysicalAddress(mPhysicalAddress); mTestLooper.dispatchAll(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); } private OneTouchPlayAction createOneTouchPlayAction(HdmiCecLocalDevicePlayback device, TestActionTimer actionTimer, TestCallback callback, boolean isCec20) { OneTouchPlayAction action = new OneTouchPlayAction(device, ADDR_TV, callback, isCec20); action.setActionTimer(actionTimer); return action; } @Test public void succeedAfterGettingPowerStatusOn_Cec14b() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, false); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = new HdmiCecMessage( ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); action.processCommand(reportPowerStatusOn); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @Test public void succeedAfterGettingTransientPowerStatus_Cec14b() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, false); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusTransientToOn = new HdmiCecMessage( ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON); action.processCommand(reportPowerStatusTransientToOn); action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); HdmiCecMessage reportPowerStatusOn = new HdmiCecMessage( ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); action.processCommand(reportPowerStatusOn); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @Test public void timeOut_Cec14b() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, false); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); for (int i = 0; i < 10; ++i) { action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS); mTestLooper.dispatchAll(); } assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT); } @Test public void succeedIfPowerStatusOn_Cec20() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV, HdmiControlManager.POWER_STATUS_ON); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, true); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @Test public void succeedIfPowerStatusUnknown_Cec20() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV, HdmiControlManager.POWER_STATUS_UNKNOWN); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, true); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = new HdmiCecMessage( ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); action.processCommand(reportPowerStatusOn); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @Test public void succeedIfPowerStatusStandby_Cec20() { HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); mLocalDevices.add(playbackDevice); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV, HdmiControlManager.POWER_STATUS_STANDBY); mTestLooper.dispatchAll(); TestActionTimer actionTimer = new TestActionTimer(); TestCallback callback = new TestCallback(); OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, true); playbackDevice.addAndStartAction(action); mTestLooper.dispatchAll(); HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( playbackDevice.mAddress, mPhysicalAddress); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, ADDR_TV); HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = new HdmiCecMessage( ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); action.processCommand(reportPowerStatusOn); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } 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 TestCallback extends IHdmiControlCallback.Stub { private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>(); @Override public void onComplete(int result) { mCallbackResult.add(result); } private int getResult() { assertThat(mCallbackResult.size()).isEqualTo(1); return mCallbackResult.get(0); } } }