Merge "Add retries to CEC detection"
This commit is contained in:
committed by
Android (Google) Code Review
commit
07b26efaf6
@@ -20,6 +20,7 @@ import android.hardware.hdmi.HdmiControlManager;
|
||||
import android.hardware.hdmi.HdmiPlaybackClient;
|
||||
import android.hardware.hdmi.HdmiPlaybackClient.DisplayStatusCallback;
|
||||
import android.hardware.hdmi.IHdmiControlCallback;
|
||||
import android.hardware.tv.cec.V1_0.SendMessageResult;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
@@ -42,6 +43,10 @@ final class DevicePowerStatusAction extends HdmiCecFeatureAction {
|
||||
private final int mTargetAddress;
|
||||
private final List<IHdmiControlCallback> mCallbacks = new ArrayList<>();
|
||||
|
||||
// Retry the power status query as it might happen when the target device is waking up. In
|
||||
// that case a device may be quite busy and can fail to respond within the 2s timeout.
|
||||
private int mRetriesOnTimeout = 1;
|
||||
|
||||
static DevicePowerStatusAction create(HdmiCecLocalDevice source,
|
||||
int targetAddress, IHdmiControlCallback callback) {
|
||||
if (source == null || callback == null) {
|
||||
@@ -68,13 +73,21 @@ final class DevicePowerStatusAction extends HdmiCecFeatureAction {
|
||||
|
||||
private void queryDevicePowerStatus() {
|
||||
sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
|
||||
mTargetAddress));
|
||||
mTargetAddress), error -> {
|
||||
// Don't retry on timeout if the remote device didn't even ACK the message. Assume
|
||||
// the device is not present or not capable of CEC.
|
||||
if (error == SendMessageResult.NACK) {
|
||||
// Got no response from TV. Report status 'unknown'.
|
||||
invokeCallback(HdmiControlManager.POWER_STATUS_UNKNOWN);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean processCommand(HdmiCecMessage cmd) {
|
||||
if (mState != STATE_WAITING_FOR_REPORT_POWER_STATUS
|
||||
|| mTargetAddress != cmd.getSource()) {
|
||||
|| mTargetAddress != cmd.getSource()) {
|
||||
return false;
|
||||
}
|
||||
if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) {
|
||||
@@ -92,6 +105,12 @@ final class DevicePowerStatusAction extends HdmiCecFeatureAction {
|
||||
return;
|
||||
}
|
||||
if (state == STATE_WAITING_FOR_REPORT_POWER_STATUS) {
|
||||
if (mRetriesOnTimeout > 0) {
|
||||
mRetriesOnTimeout--;
|
||||
start();
|
||||
return;
|
||||
}
|
||||
|
||||
// Got no response from TV. Report status 'unknown'.
|
||||
invokeCallback(HdmiControlManager.POWER_STATUS_UNKNOWN);
|
||||
finish();
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.hardware.hdmi.HdmiControlManager;
|
||||
import android.hardware.hdmi.IHdmiControlCallback;
|
||||
import android.hardware.tv.cec.V1_0.SendMessageResult;
|
||||
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 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 DevicePowerStatusAction} */
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public class DevicePowerStatusActionTest {
|
||||
|
||||
private static final int TIMEOUT_MS = HdmiConfig.TIMEOUT_MS + 1;
|
||||
|
||||
private Context mContextSpy;
|
||||
private HdmiControlService mHdmiControlService;
|
||||
private HdmiCecLocalDevice mPlaybackDevice;
|
||||
private FakeNativeWrapper mNativeWrapper;
|
||||
|
||||
private TestLooper mTestLooper = new TestLooper();
|
||||
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
|
||||
private int mPhysicalAddress;
|
||||
|
||||
private DevicePowerStatusAction mDevicePowerStatusAction;
|
||||
|
||||
@Mock private IHdmiControlCallback mCallbackMock;
|
||||
@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);
|
||||
|
||||
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HdmiCecConfig getHdmiCecConfig() {
|
||||
return hdmiCecConfig;
|
||||
}
|
||||
};
|
||||
|
||||
Looper looper = mTestLooper.getLooper();
|
||||
mHdmiControlService.setIoLooper(looper);
|
||||
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);
|
||||
mPlaybackDevice = new HdmiCecLocalDevicePlayback(
|
||||
mHdmiControlService);
|
||||
mPlaybackDevice.init();
|
||||
mLocalDevices.add(mPlaybackDevice);
|
||||
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
||||
mDevicePowerStatusAction = DevicePowerStatusAction.create(mPlaybackDevice, ADDR_TV,
|
||||
mCallbackMock);
|
||||
mTestLooper.dispatchAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryDisplayStatus_sendsRequestAndHandlesResponse() throws Exception {
|
||||
mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
HdmiCecMessage expected = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
|
||||
mPlaybackDevice.mAddress, ADDR_TV);
|
||||
assertThat(mNativeWrapper.getResultMessages()).contains(expected);
|
||||
|
||||
HdmiCecMessage response = HdmiCecMessageBuilder.buildReportPowerStatus(
|
||||
ADDR_TV, mPlaybackDevice.mAddress, HdmiControlManager.POWER_STATUS_STANDBY);
|
||||
mNativeWrapper.onCecMessage(response);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryDisplayStatus_sendsRequest_nack() throws Exception {
|
||||
mNativeWrapper.setMessageSendResult(Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS,
|
||||
SendMessageResult.NACK);
|
||||
|
||||
mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_UNKNOWN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryDisplayStatus_sendsRequest_timeout_retriesSuccessfully() throws Exception {
|
||||
mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
|
||||
|
||||
HdmiCecMessage expected = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
|
||||
mPlaybackDevice.mAddress, ADDR_TV);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
assertThat(mNativeWrapper.getResultMessages()).contains(expected);
|
||||
mNativeWrapper.clearResultMessages();
|
||||
mTestLooper.moveTimeForward(TIMEOUT_MS);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
assertThat(mNativeWrapper.getResultMessages()).contains(expected);
|
||||
|
||||
HdmiCecMessage response = HdmiCecMessageBuilder.buildReportPowerStatus(
|
||||
ADDR_TV, mPlaybackDevice.mAddress, HdmiControlManager.POWER_STATUS_STANDBY);
|
||||
mNativeWrapper.onCecMessage(response);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryDisplayStatus_sendsRequest_timeout_retriesFailure() throws Exception {
|
||||
mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
HdmiCecMessage expected = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
|
||||
mPlaybackDevice.mAddress, ADDR_TV);
|
||||
assertThat(mNativeWrapper.getResultMessages()).contains(expected);
|
||||
mNativeWrapper.clearResultMessages();
|
||||
mTestLooper.moveTimeForward(TIMEOUT_MS);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
assertThat(mNativeWrapper.getResultMessages()).contains(expected);
|
||||
mNativeWrapper.clearResultMessages();
|
||||
mTestLooper.moveTimeForward(TIMEOUT_MS);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_UNKNOWN);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user