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

Commit fb160c56 authored by Alison Chang's avatar Alison Chang Committed by Paul Colta
Browse files

HDMICEC: Add lock for CEC standby.

TCL issue 2-7 "CEC devices will not enter standby mode with TV sometimes."

When going to standby use a WakeLock that will keep the system turned on until the standby process is completed and the WakeLock is released.

Bug: 253535259
Bug: 230663730
Test: atest com.android.server.hdmi

Change-Id: Ib6cba8c59e541a4c8abd5af8ac4302e22ebf9077
parent 00da8510
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.hdmi;

import static com.android.server.hdmi.HdmiControlService.DEVICE_CLEANUP_TIMEOUT;

import android.annotation.CallSuper;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
@@ -61,9 +63,6 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
    private static final int MAX_HDMI_ACTIVE_SOURCE_HISTORY = 10;
    private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1;
    private static final int MSG_USER_CONTROL_RELEASE_TIMEOUT = 2;
    // Timeout in millisecond for device clean up (5s).
    // Normal actions timeout is 2s but some of them would have several sequence of timeout.
    private static final int DEVICE_CLEANUP_TIMEOUT = 5000;
    // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior.
    // When it expires, we can assume <User Control Release> is received.
    private static final int FOLLOWER_SAFETY_TIMEOUT = 550;
+1 −2
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Binder;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemProperties;
import android.sysprop.HdmiProperties;
import android.util.Slog;
@@ -601,7 +600,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
    }

    private class SystemWakeLock implements ActiveWakeLock {
        private final WakeLock mWakeLock;
        private final WakeLockWrapper mWakeLock;
        public SystemWakeLock() {
            mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
            mWakeLock.setReferenceCounted(false);
+57 −1
Original line number Diff line number Diff line
@@ -21,8 +21,10 @@ import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVIC
import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_DISABLED;
import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_ENABLED;
import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
import static android.hardware.hdmi.HdmiControlManager.POWER_CONTROL_MODE_NONE;
import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_DISABLED;
import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_ENABLED;
import static android.hardware.hdmi.HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED;

import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
import static com.android.server.hdmi.Constants.DISABLED;
@@ -223,6 +225,10 @@ public class HdmiControlService extends SystemService {
    public @interface WakeReason {
    }

    // Timeout in millisecond for device clean up (5s).
    // Normal actions timeout is 2s but some of them would have several sequences of timeout.
    static final int DEVICE_CLEANUP_TIMEOUT = 5000;

    @VisibleForTesting
    static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes(
            AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, "");
@@ -490,6 +496,9 @@ public class HdmiControlService extends SystemService {
    @Nullable
    private DeviceConfigWrapper mDeviceConfig;

    @Nullable
    private WakeLockWrapper mWakeLock;

    @Nullable
    private PowerManagerWrapper mPowerManager;

@@ -3014,7 +3023,7 @@ public class HdmiControlService extends SystemService {
        }
        String powerControlMode = getHdmiCecConfig().getStringValue(
                HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE);
        if (powerControlMode.equals(HdmiControlManager.POWER_CONTROL_MODE_NONE)) {
        if (powerControlMode.equals(POWER_CONTROL_MODE_NONE)) {
            return false;
        }
        int hdmiCecEnabled = getHdmiCecConfig().getIntValue(
@@ -3655,6 +3664,9 @@ public class HdmiControlService extends SystemService {
    @ServiceThreadOnly
    @VisibleForTesting
    protected void onStandby(final int standbyAction) {
        if (shouldAcquireWakeLock()) {
            acquireWakeLock();
        }
        mWakeUpMessageReceived = false;
        assertRunOnServiceThread();
        mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY,
@@ -3762,6 +3774,7 @@ public class HdmiControlService extends SystemService {
                    return;
                }

                releaseWakeLock();
                if (isAudioSystemDevice() || !isPowerStandby()) {
                    return;
                }
@@ -3780,6 +3793,49 @@ public class HdmiControlService extends SystemService {
        mStandbyMessageReceived = false;
    }

    private boolean shouldAcquireWakeLock() {
        boolean sendStandbyOnSleep = false;
        if (tv() != null) {
            sendStandbyOnSleep = mHdmiCecConfig.getIntValue(
                    HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
                    == TV_SEND_STANDBY_ON_SLEEP_ENABLED;
        } else if (playback() != null) {
            sendStandbyOnSleep = !mHdmiCecConfig.getStringValue(
                            HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)
                    .equals(POWER_CONTROL_MODE_NONE);
        }

        return isCecControlEnabled() && isPowerOnOrTransient() && sendStandbyOnSleep;
    }

    /**
     * Acquire the wake lock used to hold the thread until the standby process is finished.
     */
    @VisibleForTesting
    protected void acquireWakeLock() {
        releaseWakeLock();
        mWakeLock = mPowerManager.newWakeLock(
                PowerManager.PARTIAL_WAKE_LOCK, TAG);
        mWakeLock.acquire(DEVICE_CLEANUP_TIMEOUT);
    }

    /**
     * Release the wake lock acquired when the standby process started.
     */
    @VisibleForTesting
    protected void releaseWakeLock() {
        if (mWakeLock != null) {
            try {
                if (mWakeLock.isHeld()) {
                    mWakeLock.release();
                }
            } catch (RuntimeException e) {
                Slog.w(TAG, "Exception when releasing wake lock.");
            }
            mWakeLock = null;
        }
    }

    @VisibleForTesting
    void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {
        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, vendorId);
+49 −3
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.server.hdmi;

import android.content.Context;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;

/**
 * Abstraction around {@link PowerManager} to allow faking PowerManager in tests.
@@ -44,7 +43,54 @@ public class PowerManagerWrapper {
        mPowerManager.goToSleep(time, reason, flags);
    }

    WakeLock newWakeLock(int levelAndFlags, String tag) {
        return mPowerManager.newWakeLock(levelAndFlags, tag);
    WakeLockWrapper newWakeLock(int levelAndFlags, String tag) {
        return new DefaultWakeLockWrapper(mPowerManager.newWakeLock(levelAndFlags, tag));
    }

    /**
     * "Default" wrapper for {@link PowerManager.WakeLock}, as opposed to a "Fake" wrapper for
     * testing - see {@link FakePowerManagerWrapper.FakeWakeLockWrapper}.
     *
     * Stores an instance of {@link PowerManager.WakeLock} and directly passes method calls to that
     * instance.
     */
    public static class DefaultWakeLockWrapper implements WakeLockWrapper {

        private static final String TAG = "DefaultWakeLockWrapper";

        private final PowerManager.WakeLock mWakeLock;

        private DefaultWakeLockWrapper(PowerManager.WakeLock wakeLock) {
            mWakeLock = wakeLock;
        }

        @Override
        public void acquire(long timeout) {
            mWakeLock.acquire(timeout);
        }

        @Override
        public void acquire() {
            mWakeLock.acquire();
        }

        /**
         * @throws RuntimeException WakeLock can throw this exception if it is not released
         * successfully.
         */
        @Override
        public void release() throws RuntimeException {
            mWakeLock.release();
        }

        @Override
        public boolean isHeld() {
            return mWakeLock.isHeld();
        }

        @Override
        public void setReferenceCounted(boolean value) {
            mWakeLock.setReferenceCounted(value);
        }
    }
}
+52 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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;

/**
 * Interface with the methods from {@link PowerManager.WakeLock} used by the HDMI control framework.
 * Allows the class to be faked for the tests.
 *
 * See implementations {@link DefaultWakeLockWrapper} and
 * {@link FakePowerManagerWrapper.FakeWakeLockWrapper}.
 */
public interface WakeLockWrapper {

    /**
     * Wraps {@link PowerManager.WakeLock#acquire(long)};
     */
    void acquire(long timeout);

    /**
     * Wraps {@link PowerManager.WakeLock#acquire()};
     */
    void acquire();

    /**
     * Wraps {@link PowerManager.WakeLock#release()};
     */
    void release();

    /**
     * Wraps {@link PowerManager.WakeLock#isHeld()};
     */
    boolean isHeld();

    /**
     * Wraps {@link PowerManager.WakeLock#setReferenceCounted(boolean)};
     */
    void setReferenceCounted(boolean value);
}
Loading