Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b06ff485 authored by Yan Han's avatar Yan Han Committed by Android (Google) Code Review
Browse files

Merge "Add delayed Standby for Playback devices on hotplug out"

parents 8a40b4bf dadce2bb
Loading
Loading
Loading
Loading
+36 −2
Original line number Diff line number Diff line
@@ -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);
            }
        }
    }

+24 −10
Original line number Diff line number Diff line
@@ -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,20 +3252,21 @@ public class HdmiControlService extends SystemService {
        assertRunOnServiceThread();
        Slog.v(TAG, "onPendingActionsCleared");

        if (!mPowerStatusController.isPowerStatusTransientToStandby()) {
            return;
        }
        if (mPowerStatusController.isPowerStatusTransientToStandby()) {
            mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
            for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
                device.onStandby(mStandbyMessageReceived, standbyAction);
            }
        mStandbyMessageReceived = false;
            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;
    }

    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
        try {
+41 −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 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);
    }
}
+37 −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;

/**
 * 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;
    }
}
+40 −0
Original line number Diff line number Diff line
@@ -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();
    }
}