Add delayed Standby for Playback devices on hotplug out

Upon receiving a hotplug out event, a Playback device starts a 30 second
countdown. If no user interaction with the device occurs during that
time, it goes to Standby. Otherwise, it will check again every
30 seconds until a hotplug in event is received.

Test: atest HdmiCecLocalDevicePlaybackTest; manual
Bug: 206934433
Change-Id: If4a9f62c7ace93ee82a28c875d2efc7fa1dd635f
This commit is contained in:
Yan Han
2021-11-17 13:21:05 +01:00
parent 7644c9c20c
commit dadce2bb19
5 changed files with 178 additions and 12 deletions

View File

@@ -21,6 +21,7 @@ import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemProperties;
@@ -47,6 +48,10 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
private static final boolean SET_MENU_LANGUAGE =
HdmiProperties.set_menu_language_enabled().orElse(false);
// How long to wait after hotplug out before possibly going to Standby.
@VisibleForTesting
static final long STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS = 30_000;
// Used to keep the device awake while it is the active source. For devices that
// cannot wake up via CEC commands, this address the inconvenience of having to
// turn them on. True by default, and can be disabled (i.e. device can go to sleep
@@ -55,6 +60,9 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
// Lazily initialized - should call getWakeLock() to get the instance.
private ActiveWakeLock mWakeLock;
// Handler for queueing a delayed Standby runnable after hotplug out.
private Handler mDelayedStandbyHandler;
// Determines what action should be taken upon receiving Routing Control messages.
@VisibleForTesting
protected HdmiProperties.playback_device_action_on_routing_control_values
@@ -64,6 +72,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
HdmiCecLocalDevicePlayback(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
mDelayedStandbyHandler = new Handler(service.getServiceLooper());
}
@Override
@@ -195,9 +205,33 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
void onHotplug(int portId, boolean connected) {
assertRunOnServiceThread();
mCecMessageCache.flushAll();
// We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3.
if (!connected) {
if (connected) {
mDelayedStandbyHandler.removeCallbacksAndMessages(null);
} else {
// We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3
getWakeLock().release();
mDelayedStandbyHandler.removeCallbacksAndMessages(null);
mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(),
STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
}
}
/**
* Runnable for going to Standby if the device has been inactive for a certain amount of time.
* Posts a new instance of itself as a delayed message if the device was active.
*/
private class DelayedStandbyRunnable implements Runnable {
@Override
public void run() {
if (mService.getPowerManagerInternal().wasDeviceIdleFor(
STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS)) {
mService.standby();
} else {
mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(),
STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
}
}
}

View File

@@ -407,6 +407,9 @@ public class HdmiControlService extends SystemService {
@Nullable
private PowerManagerWrapper mPowerManager;
@Nullable
private PowerManagerInternalWrapper mPowerManagerInternal;
@Nullable
private Looper mIoLooper;
@@ -734,6 +737,7 @@ public class HdmiControlService extends SystemService {
mTvInputManager = (TvInputManager) getContext().getSystemService(
Context.TV_INPUT_SERVICE);
mPowerManager = new PowerManagerWrapper(getContext());
mPowerManagerInternal = new PowerManagerInternalWrapper();
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
runOnServiceThread(this::bootCompleted);
}
@@ -758,10 +762,19 @@ public class HdmiControlService extends SystemService {
mPowerManager = powerManager;
}
@VisibleForTesting
void setPowerManagerInternal(PowerManagerInternalWrapper powerManagerInternal) {
mPowerManagerInternal = powerManagerInternal;
}
PowerManagerWrapper getPowerManager() {
return mPowerManager;
}
PowerManagerInternalWrapper getPowerManagerInternal() {
return mPowerManagerInternal;
}
/**
* Called when the initialization of local devices is complete.
*/
@@ -3239,18 +3252,19 @@ public class HdmiControlService extends SystemService {
assertRunOnServiceThread();
Slog.v(TAG, "onPendingActionsCleared");
if (!mPowerStatusController.isPowerStatusTransientToStandby()) {
return;
}
mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
device.onStandby(mStandbyMessageReceived, standbyAction);
if (mPowerStatusController.isPowerStatusTransientToStandby()) {
mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
device.onStandby(mStandbyMessageReceived, standbyAction);
}
if (!isAudioSystemDevice()) {
mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
}
}
// Always reset this flag to set up for the next standby
mStandbyMessageReceived = false;
if (!isAudioSystemDevice()) {
mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
}
}
private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {

View File

@@ -0,0 +1,41 @@
/*
* 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 android.os.PowerManagerInternal;
import com.android.server.LocalServices;
/**
* Abstraction around {@link PowerManagerInternal} to allow faking it in tests.
*/
public class PowerManagerInternalWrapper {
private static final String TAG = "PowerManagerInternalWrapper";
private PowerManagerInternal mPowerManagerInternal;
public PowerManagerInternalWrapper() {
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
}
/**
* Wraps {@link PowerManagerInternal#wasDeviceIdleFor(long)}
*/
public boolean wasDeviceIdleFor(long ms) {
return mPowerManagerInternal.wasDeviceIdleFor(ms);
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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;
/**
* Fake class which stubs PowerManagerInternalWrapper (useful for testing).
*/
public class FakePowerManagerInternalWrapper extends PowerManagerInternalWrapper {
private long mIdleDurationMs = -1;
/**
* Sets the duration (in milliseconds) that this device has been idle for.
*/
public void setIdleDuration(long idleDurationMs) {
mIdleDurationMs = idleDurationMs;
}
@Override
public boolean wasDeviceIdleFor(long ms) {
return ms <= mIdleDurationMs;
}
}

View File

@@ -76,6 +76,8 @@ public class HdmiCecLocalDevicePlaybackTest {
private int mPlaybackLogicalAddress;
private boolean mWokenUp;
private boolean mActiveMediaSessionsPaused;
private FakePowerManagerInternalWrapper mPowerManagerInternal =
new FakePowerManagerInternalWrapper();
@Before
public void setUp() {
@@ -146,6 +148,7 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiControlService.initService();
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mPlaybackPhysicalAddress = 0x2000;
mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
@@ -1969,4 +1972,41 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortPressed);
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortReleased);
}
@Test
public void onHotplugInAfterHotplugOut_noStandbyAfterDelay() {
mPowerManager.setInteractive(true);
mNativeWrapper.onHotplugEvent(1, false);
mTestLooper.dispatchAll();
mTestLooper.moveTimeForward(
HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS / 2);
mNativeWrapper.onHotplugEvent(1, true);
mTestLooper.dispatchAll();
mPowerManagerInternal.setIdleDuration(
HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
mTestLooper.moveTimeForward(HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
mTestLooper.dispatchAll();
assertThat(mPowerManager.isInteractive()).isTrue();
}
@Test
public void onHotplugOut_standbyAfterDelay_onlyAfterDeviceIdle() {
mPowerManager.setInteractive(true);
mNativeWrapper.onHotplugEvent(1, false);
mTestLooper.dispatchAll();
mPowerManagerInternal.setIdleDuration(0);
mTestLooper.moveTimeForward(HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
mTestLooper.dispatchAll();
assertThat(mPowerManager.isInteractive()).isTrue();
mPowerManagerInternal.setIdleDuration(
HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
mTestLooper.moveTimeForward(HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
mTestLooper.dispatchAll();
assertThat(mPowerManager.isInteractive()).isFalse();
}
}