diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index c3354e1d7e0f6..1794df3b602e0 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -310,6 +310,11 @@ public class HdmiControlService extends SystemService { // Invoke once new local device is ready. private IHdmiControlCallback mDisplayStatusCallback = null; + @Nullable + // Save callback when the device is still under logcial address allocation + // Invoke once new local device is ready. + private IHdmiControlCallback mOtpCallbackPendingAddressAllocation = null; + @Nullable private HdmiCecController mCecController; @@ -785,17 +790,21 @@ public class HdmiControlService extends SystemService { // Address allocation completed for all devices. Notify each device. if (allocatingDevices.size() == ++finished[0]) { mAddressAllocated = true; - // Reinvoke the saved display status callback once the local device is ready. - if (mDisplayStatusCallback != null) { - queryDisplayStatus(mDisplayStatusCallback); - mDisplayStatusCallback = null; - } if (initiatedBy != INITIATED_BY_HOTPLUG) { // In case of the hotplug we don't call onInitializeCecComplete() // since we reallocate the logical address only. onInitializeCecComplete(initiatedBy); } notifyAddressAllocated(allocatedDevices, initiatedBy); + // Reinvoke the saved display status callback once the local device is ready. + if (mDisplayStatusCallback != null) { + queryDisplayStatus(mDisplayStatusCallback); + mDisplayStatusCallback = null; + } + if (mOtpCallbackPendingAddressAllocation != null) { + oneTouchPlay(mOtpCallbackPendingAddressAllocation); + mOtpCallbackPendingAddressAllocation = null; + } mCecMessageBuffer.processMessages(); } } @@ -2246,8 +2255,16 @@ public class HdmiControlService extends SystemService { } @ServiceThreadOnly - private void oneTouchPlay(final IHdmiControlCallback callback) { + @VisibleForTesting + protected void oneTouchPlay(final IHdmiControlCallback callback) { assertRunOnServiceThread(); + if (!mAddressAllocated) { + mOtpCallbackPendingAddressAllocation = callback; + Slog.d(TAG, "Local device is under address allocation. " + + "Save OTP callback for later process."); + return; + } + HdmiCecLocalDeviceSource source = playback(); if (source == null) { source = audioSystem(); diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java index c8fc5fc96e59e..4962af176f18a 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java @@ -77,7 +77,6 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress)); broadcastActiveSource(); queryDevicePowerStatus(); - mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; addTimer(mState, HdmiConfig.TIMEOUT_MS); return true; } @@ -99,6 +98,7 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { } private void queryDevicePowerStatus() { + mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), mTargetAddress)); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java new file mode 100644 index 0000000000000..c6cf9b116a1df --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2019 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 android.os.SystemClock.sleep; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertEquals; + +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiControlCallback; +import android.os.Looper; +import android.os.SystemProperties; +import android.os.test.TestLooper; +import android.util.Slog; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import java.util.ArrayList; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for {@link HdmiControlServiceBinderAPITest} class. + */ +@SmallTest +@RunWith(JUnit4.class) +public class HdmiControlServiceBinderAPITest { + + private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice { + + private boolean mCanGoToStandby; + private boolean mIsStandby; + private boolean mIsDisabled; + + protected HdmiCecLocalDeviceMyDevice(HdmiControlService service, int deviceType) { + super(service, deviceType); + } + + @Override + protected void onAddressAllocated(int logicalAddress, int reason) { + } + + @Override + protected int getPreferredAddress() { + return 0; + } + + @Override + protected void setPreferredAddress(int addr) { + } + + @Override + protected boolean canGoToStandby() { + return mCanGoToStandby; + } + + @Override + protected void disableDevice( + boolean initiatedByCec, final PendingActionClearedCallback originalCallback) { + mIsDisabled = true; + originalCallback.onCleared(this); + } + + @Override + protected void onStandby(boolean initiatedByCec, int standbyAction) { + mIsStandby = true; + } + + protected boolean isStandby() { + return mIsStandby; + } + + protected boolean isDisabled() { + return mIsDisabled; + } + + protected void setCanGoToStandby(boolean canGoToStandby) { + mCanGoToStandby = canGoToStandby; + } + } + + private static final String TAG = "HdmiControlServiceBinderAPITest"; + private HdmiControlService mHdmiControlService; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDevicePlayback mPlaybackDevice; + private FakeNativeWrapper mNativeWrapper; + private Looper mMyLooper; + private TestLooper mTestLooper = new TestLooper(); + private ArrayList mLocalDevices = new ArrayList<>(); + private HdmiPortInfo[] mHdmiPortInfo; + private int mResult; + private int mPowerStatus; + + @Before + public void SetUp() { + mHdmiControlService = + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + @Override + void sendCecCommand(HdmiCecMessage command) { + switch (command.getOpcode()) { + case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS: + HdmiCecMessage message = + HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_ON); + handleCecCommand(message); + break; + default: + return; + } + } + + @Override + boolean isPowerStandby() { + return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; + } + }; + mMyLooper = mTestLooper.getLooper(); + + mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlService) { + @Override + void setIsActiveSource(boolean on) { + mIsActiveSource = on; + } + + @Override + protected void wakeUpIfActiveSource() {} + + @Override + protected void setPreferredAddress(int addr) {} + + @Override + protected int getPreferredAddress() { + return Constants.ADDR_PLAYBACK_1; + } + }; + mPlaybackDevice.init(); + + mHdmiControlService.setIoLooper(mMyLooper); + + mNativeWrapper = new FakeNativeWrapper(); + mHdmiCecController = + HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); + mHdmiControlService.setCecController(mHdmiCecController); + mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + + mLocalDevices.add(mPlaybackDevice); + mHdmiPortInfo = new HdmiPortInfo[1]; + mHdmiPortInfo[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); + mNativeWrapper.setPortInfo(mHdmiPortInfo); + mHdmiControlService.initPortInfo(); + mResult = -1; + mPowerStatus = HdmiControlManager.POWER_STATUS_ON; + + mTestLooper.dispatchAll(); + } + + @Test + public void oneTouchPlay_addressNotAllocated() { + assertThat(mHdmiControlService.isAddressAllocated()).isFalse(); + mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + mResult = result; + } + }); + assertEquals(mResult, -1); + assertThat(mPlaybackDevice.mIsActiveSource).isFalse(); + + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); + assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS); + assertThat(mPlaybackDevice.mIsActiveSource).isTrue(); + } + + @Test + public void oneTouchPlay_addressAllocated() { + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); + mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + mResult = result; + } + }); + assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS); + assertThat(mPlaybackDevice.mIsActiveSource).isTrue(); + } +}