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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user