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

Commit 09ad7ffc authored by Paul Colța's avatar Paul Colța Committed by Android (Google) Code Review
Browse files

Merge "HDMICEC: Add deviceSelect API for Playback devices"

parents a1fd7546 dcf47e90
Loading
Loading
Loading
Loading
+251 −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.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Handles an action that selects a logical device as a new active source.
 *
 * Triggered by {@link HdmiPlaybackClient}, attempts to select the given target device
 * for a new active source. A >Routing Change< command is issued in order to select
 * the target device. If that doesn't succeed a >Set Stream Path< command is sent.
 * It does its best to wake up the target in standby mode, before issuing the device
 * select command.
 */
final class DeviceSelectActionFromPlayback extends HdmiCecFeatureAction {
    private static final String TAG = "DeviceSelectActionFromPlayback";

    // Time in milliseconds we wait for the device power status to switch to 'Standby'
    private static final int TIMEOUT_TRANSIT_TO_STANDBY_MS = 5 * 1000;

    // Time in milliseconds we wait for the device power status to turn to 'On'.
    private static final int TIMEOUT_POWER_ON_MS = 5 * 1000;

    // The number of times we try to wake up the target device before we give up
    // and just send <Routing Change>.
    private static final int LOOP_COUNTER_MAX = 2;

    // State in which we wait for <Report Power Status> to come in response to the command
    // <Give Device Power Status> we have sent.
    @VisibleForTesting
    static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1;

    // State in which we wait for the device power status to switch to 'Standby'.
    // We wait till the status becomes 'Standby' before we send <Routing Change>
    // to wake up the device again.
    private static final int STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY = 2;

    // State in which we wait for the device power status to switch to 'on'. We wait
    // maximum 100 seconds (20 * 5) before we give up and just send <Set Stream Path>.
    @VisibleForTesting
    static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3;

    // State in which we wait for <Active Source> to come in response to the command
    // <Routing Change> we have sent.
    @VisibleForTesting
    static final int STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE = 4;

    // State in which we wait for <Active Source> to come in response to the command
    // <Set Stream Path> we have sent.
    @VisibleForTesting
    private static final int STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_SET_STREAM_PATH = 5;

    private final HdmiDeviceInfo mTarget;
    private final HdmiCecMessage mGivePowerStatus;
    private final boolean mIsCec20;

    private int mPowerStatusCounter = 0;

    /**
     * Constructor.
     *
     * @param source {@link HdmiCecLocalDevice} instance
     * @param target target logical device that will be a new active source
     * @param callback callback object
     */
    DeviceSelectActionFromPlayback(HdmiCecLocalDevicePlayback source, HdmiDeviceInfo target,
            IHdmiControlCallback callback) {
        this(source, target, callback,
                source.getDeviceInfo().getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0
                        && target.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0);
    }

    @VisibleForTesting
    DeviceSelectActionFromPlayback(HdmiCecLocalDevicePlayback source, HdmiDeviceInfo target,
            IHdmiControlCallback callback, boolean isCec20) {
        super(source, callback);
        mTarget = target;
        mGivePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
                getSourceAddress(), getTargetAddress());
        mIsCec20 = isCec20;
    }

    private int getTargetAddress() {
        return mTarget.getLogicalAddress();
    }

    private int getTargetPath() {
        return mTarget.getPhysicalAddress();
    }

    @Override
    public boolean start() {
        // This <Routing Change> message wakes up the target device in most cases.
        sendRoutingChange();

        if (!mIsCec20) {
            queryDevicePowerStatus();
        } else {
            int targetPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
            HdmiDeviceInfo targetDevice = localDevice().mService.getHdmiCecNetwork()
                    .getCecDeviceInfo(getTargetAddress());
            if (targetDevice != null) {
                targetPowerStatus = targetDevice.getDevicePowerStatus();
            }
            if (targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
                queryDevicePowerStatus();
            } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
                mState = STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE;
                addTimer(mState, HdmiConfig.TIMEOUT_MS);
                return true;
            }
        }

        mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
        addTimer(mState, HdmiConfig.TIMEOUT_MS);
        return true;
    }

    private void queryDevicePowerStatus() {
        sendCommand(
                mGivePowerStatus,
                new HdmiControlService.SendMessageCallback() {
                    @Override
                    public void onSendCompleted(int error) {
                        if (error != SendMessageResult.SUCCESS) {
                            finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
                            return;
                        }
                    }
                });
    }

    @Override
    public boolean processCommand(HdmiCecMessage cmd) {
        if (cmd.getSource() != getTargetAddress()) {
            return false;
        }
        int opcode = cmd.getOpcode();
        byte[] params = cmd.getParams();
        if (opcode == Constants.MESSAGE_ACTIVE_SOURCE) {
            // The target device was successfully set as the active source
            finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
            return true;
        }
        if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS
                && opcode == Constants.MESSAGE_REPORT_POWER_STATUS) {
            return handleReportPowerStatus(params[0]);
        }
        return false;
    }

    private boolean handleReportPowerStatus(int powerStatus) {
        switch (powerStatus) {
            case HdmiControlManager.POWER_STATUS_ON:
                selectDevice();
                return true;
            case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
                if (mPowerStatusCounter < 4) {
                    mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY;
                    addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS);
                } else {
                    selectDevice();
                }
                return true;
            case HdmiControlManager.POWER_STATUS_STANDBY:
                if (mPowerStatusCounter == 0) {
                    sendRoutingChange();
                    mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
                    addTimer(mState, TIMEOUT_POWER_ON_MS);
                } else {
                    selectDevice();
                }
                return true;
            case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
                if (mPowerStatusCounter < LOOP_COUNTER_MAX) {
                    mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
                    addTimer(mState, TIMEOUT_POWER_ON_MS);
                } else {
                    selectDevice();
                }
                return true;
        }
        return false;
    }

    private void selectDevice() {
        sendRoutingChange();
        mState = STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE;
        addTimer(mState, HdmiConfig.TIMEOUT_MS);
    }

    @Override
    void handleTimerEvent(int timeoutState) {
        if (mState != timeoutState) {
            Slog.w(TAG, "Timer in a wrong state. Ignored.");
            return;
        }
        switch (mState) {
            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
                selectDevice();
                addTimer(mState, HdmiConfig.TIMEOUT_MS);
                break;
            case STATE_WAIT_FOR_DEVICE_POWER_ON:
                mPowerStatusCounter++;
                queryDevicePowerStatus();
                mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
                addTimer(mState, HdmiConfig.TIMEOUT_MS);
                break;
            case STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE:
                sendSetStreamPath();
                mState = STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_SET_STREAM_PATH;
                addTimer(mState, HdmiConfig.TIMEOUT_MS);
                break;
            case STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_SET_STREAM_PATH:
                finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
                break;
        }
    }

    private void sendRoutingChange() {
        sendCommand(HdmiCecMessageBuilder.buildRoutingChange(getSourceAddress(),
                playback().getActiveSource().physicalAddress, getTargetPath()));
    }

    private void sendSetStreamPath() {
        sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(),
                getTargetPath()));
    }
}
+14 −0
Original line number Diff line number Diff line
@@ -260,6 +260,20 @@ abstract class HdmiCecLocalDevice {
        return onMessage(message);
    }

    @ServiceThreadOnly
    @VisibleForTesting
    protected boolean isAlreadyActiveSource(HdmiDeviceInfo targetDevice, int targetAddress,
            IHdmiControlCallback callback) {
        ActiveSource active = getActiveSource();
        if (targetDevice.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON
                && active.isValid()
                && targetAddress == active.logicalAddress) {
            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
            return true;
        }
        return false;
    }

    @ServiceThreadOnly
    @Constants.HandleMessageResult
    protected final int onMessage(HdmiCecMessage message) {
+33 −0
Original line number Diff line number Diff line
@@ -124,6 +124,39 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
                String.valueOf(addr));
    }

    /**
     * Performs the action 'device select' or 'one touch play' initiated by a Playback device.
     *
     * @param id id of HDMI device to select
     * @param callback callback object to report the result with
     */
    @ServiceThreadOnly
    void deviceSelect(int id, IHdmiControlCallback callback) {
        assertRunOnServiceThread();
        synchronized (mLock) {
            if (id == getDeviceInfo().getId()) {
                mService.oneTouchPlay(callback);
                return;
            }
        }
        HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id);
        if (targetDevice == null) {
            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
            return;
        }
        int targetAddress = targetDevice.getLogicalAddress();
        if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
            return;
        }
        if (!mService.isControlEnabled()) {
            setActiveSource(targetDevice, "HdmiCecLocalDevicePlayback#deviceSelect()");
            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
            return;
        }
        removeAction(DeviceSelectActionFromPlayback.class);
        addAndStartAction(new DeviceSelectActionFromPlayback(this, targetDevice, callback));
    }

    @Override
    @ServiceThreadOnly
    void onHotplug(int portId, boolean connected) {
+1 −5
Original line number Diff line number Diff line
@@ -242,11 +242,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
            return;
        }
        int targetAddress = targetDevice.getLogicalAddress();
        ActiveSource active = getActiveSource();
        if (targetDevice.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON
                && active.isValid()
                && targetAddress == active.logicalAddress) {
            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
        if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
            return;
        }
        if (targetAddress == Constants.ADDR_INTERNAL) {
+16 −11
Original line number Diff line number Diff line
@@ -1685,7 +1685,8 @@ public class HdmiControlService extends SystemService {
                        return;
                    }
                    HdmiCecLocalDeviceTv tv = tv();
                    if (tv == null) {
                    HdmiCecLocalDevicePlayback playback = playback();
                    if (tv == null && playback == null) {
                        if (!mAddressAllocated) {
                            mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
                                    HdmiControlService.this, deviceId, callback));
@@ -1698,6 +1699,7 @@ public class HdmiControlService extends SystemService {
                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
                        return;
                    }
                    if (tv != null) {
                        HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
                        if (device != null) {
                            if (device.getPortId() == tv.getActivePortId()) {
@@ -1712,6 +1714,9 @@ public class HdmiControlService extends SystemService {
                            return;
                        }
                        tv.deviceSelect(deviceId, callback);
                        return;
                    }
                    playback.deviceSelect(deviceId, callback);
                }
            });
        }
Loading