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

Commit 3bb41a20 authored by Jungshik Jang's avatar Jungshik Jang Committed by Android (Google) Code Review
Browse files

Merge "Implement handlers for system information query command."

parents 71234f14 a1fa91fe
Loading
Loading
Loading
Loading
+0 −33
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@
 */
package com.android.server.hdmi;

import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecMessage;
import android.os.Handler;
import android.os.Looper;
@@ -155,23 +154,10 @@ abstract class FeatureAction {
        mActionTimer.sendTimerMessage(state, delayMillis);
    }

    static HdmiCecMessage buildCommand(int src, int dst, int opcode, byte[] params) {
        return new HdmiCecMessage(src, dst, opcode, params);
    }

    // Build a CEC command that does not have parameter.
    static HdmiCecMessage buildCommand(int src, int dst, int opcode) {
        return new HdmiCecMessage(src, dst, opcode, HdmiCecMessage.EMPTY_PARAM);
    }

    protected final void sendCommand(HdmiCecMessage cmd) {
        mService.sendCecCommand(cmd);
    }

    protected final void sendBroadcastCommand(int opcode, byte[] param) {
        sendCommand(buildCommand(mSourceAddress, HdmiCec.ADDR_BROADCAST, opcode, param));
    }

    /**
     * Finish up the action. Reset the state, and remove itself from the action queue.
     */
@@ -189,23 +175,4 @@ abstract class FeatureAction {
    private void removeAction(FeatureAction action) {
        mService.removeAction(action);
    }

    // Utility methods for generating parameter byte arrays for CEC commands.
    protected static byte[] uiCommandParam(int uiCommand) {
        return new byte[] {(byte) uiCommand};
    }

    protected static byte[] physicalAddressParam(int physicalAddress) {
        return new byte[] {
                (byte) ((physicalAddress >> 8) & 0xFF),
                (byte) (physicalAddress & 0xFF)
        };
    }

    protected static byte[] pathPairParam(int oldPath, int newPath) {
        return new byte[] {
                (byte) ((oldPath >> 8) & 0xFF), (byte) (oldPath & 0xFF),
                (byte) ((newPath >> 8) & 0xFF), (byte) (newPath & 0xFF)
        };
    }
}
+28 −20
Original line number Diff line number Diff line
@@ -55,15 +55,6 @@ final class HdmiCecController {
    // A message to report allocated logical address to main control looper.
    private final static int MSG_REPORT_LOGICAL_ADDRESS = 2;

    // TODO: move these values to HdmiCec.java once make it internal constant class.
    // CEC's ABORT reason values.
    private static final int ABORT_UNRECOGNIZED_MODE = 0;
    private static final int ABORT_NOT_IN_CORRECT_MODE = 1;
    private static final int ABORT_CANNOT_PROVIDE_SOURCE = 2;
    private static final int ABORT_INVALID_OPERAND = 3;
    private static final int ABORT_REFUSED = 4;
    private static final int ABORT_UNABLE_TO_DETERMINE = 5;

    private static final int NUM_LOGICAL_ADDRESS = 16;

    // TODO: define other constants for errors.
@@ -80,10 +71,16 @@ final class HdmiCecController {
    // interacts with HAL.
    private long mNativePtr;

    private HdmiControlService mService;

    // Map-like container of all cec devices. A logical address of device is
    // used as key of container.
    private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos =
            new SparseArray<HdmiCecDeviceInfo>();
    // Set-like container for all local devices' logical address.
    // Key and value are same.
    private final SparseArray<Integer> mLocalLogicalAddresses =
            new SparseArray<Integer>();

    // Private constructor.  Use HdmiCecController.create().
    private HdmiCecController() {
@@ -325,6 +322,7 @@ final class HdmiCecController {
     */
    int addLogicalAddress(int newLogicalAddress) {
        if (HdmiCec.isValidAddress(newLogicalAddress)) {
            mLocalLogicalAddresses.append(newLogicalAddress, newLogicalAddress);
            return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
        } else {
            return -1;
@@ -337,6 +335,9 @@ final class HdmiCecController {
     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
     */
    void clearLogicalAddress() {
        // TODO: consider to backup logical address so that new logical address
        // allocation can use it as preferred address.
        mLocalLogicalAddresses.clear();
        nativeClearLogicalAddress(mNativePtr);
    }

@@ -371,30 +372,37 @@ final class HdmiCecController {
    }

    private void init(HdmiControlService service, long nativePtr) {
        mService = service;
        mIoHandler = new IoHandler(service.getServiceLooper());
        mControlHandler = new ControlHandler(service.getServiceLooper());
        mNativePtr = nativePtr;
    }

    private boolean isAcceptableAddress(int address) {
        // Can access command targeting devices available in local device or
        // broadcast command.
        return address == HdmiCec.ADDR_BROADCAST
                || mLocalLogicalAddresses.get(address) != null;
    }

    private void onReceiveCommand(HdmiCecMessage message) {
        // TODO: Handle message according to opcode type.
        if (isAcceptableAddress(message.getDestination()) &&
                mService.handleCecCommand(message)) {
            return;
        }

        // TODO: Use device's source address for broadcast message.
        int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ?
                message.getDestination() : 0;
        // Reply <Feature Abort> to initiator (source) for all requests.
        sendFeatureAbort(sourceAddress, message.getSource(), message.getOpcode(),
                ABORT_REFUSED);
    }
        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand
                (sourceAddress, message.getSource(), message.getOpcode(),
                        HdmiCecMessageBuilder.ABORT_REFUSED);
        sendCommand(cecMessage);

    private void sendFeatureAbort(int srcAddress, int destAddress, int originalOpcode,
            int reason) {
        byte[] params = new byte[2];
        params[0] = (byte) originalOpcode;
        params[1] = (byte) reason;
    }

        HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, destAddress,
                HdmiCec.MESSAGE_FEATURE_ABORT, params);
    void sendCommand(HdmiCecMessage cecMessage) {
        Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
        mIoHandler.sendMessage(message);
    }
+212 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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.HdmiCec;
import android.hardware.hdmi.HdmiCecMessage;

import java.io.UnsupportedEncodingException;

/**
 * A helper class to build {@link HdmiCecMessage} from various cec commands.
 */
public class HdmiCecMessageBuilder {
    // TODO: move these values to HdmiCec.java once make it internal constant class.
    // CEC's ABORT reason values.
    static final int ABORT_UNRECOGNIZED_MODE = 0;
    static final int ABORT_NOT_IN_CORRECT_MODE = 1;
    static final int ABORT_CANNOT_PROVIDE_SOURCE = 2;
    static final int ABORT_INVALID_OPERAND = 3;
    static final int ABORT_REFUSED = 4;
    static final int ABORT_UNABLE_TO_DETERMINE = 5;

    private static final int OSD_NAME_MAX_LENGTH = 13;

    private HdmiCecMessageBuilder() {}

    /**
     * Build &lt;Feature Abort&gt; command. &lt;Feature Abort&gt; consists of
     * 1 byte original opcode and 1 byte reason fields with basic fields.
     *
     * @param src source address of command
     * @param dest destination address of command
     * @param originalOpcode original opcode causing feature abort
     * @param reason reason of feature abort
     * @return newly created {@link HdmiCecMessage}
     */
    static HdmiCecMessage buildFeatureAbortCommand(int src, int dest, int originalOpcode,
            int reason) {
        byte[] params = new byte[] {
                (byte) originalOpcode,
                (byte) reason,
        };
        return buildCommand(src, dest, HdmiCec.MESSAGE_FEATURE_ABORT, params);
    }

    /**
     * Build &lt;Get Osd Name&gt; command.
     *
     * @param src source address of command
     * @param dest destination address of command
     * @return newly created {@link HdmiCecMessage}
     */
    static HdmiCecMessage buildGetOsdNameCommand(int src, int dest) {
        return buildCommand(src, dest, HdmiCec.MESSAGE_GET_OSD_NAME);
    }

    /**
     * Build &lt;Give Vendor Id Command&gt; command.
     *
     * @param src source address of command
     * @param dest destination address of command
     * @return newly created {@link HdmiCecMessage}
     */
    static HdmiCecMessage buildGiveDeviceVendorIdCommand(int src, int dest) {
        return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID);
    }

    /**
     * Build &lt;Set Menu Language &gt; command.
     *
     * <p>This is a broadcast message sent to all devices on the bus.
     *
     * @param src source address of command
     * @param language 3-letter ISO639-2 based language code
     * @return newly created {@link HdmiCecMessage} if language is valid.
     *         Otherwise, return null
     */
    static HdmiCecMessage buildSetMenuLanguageCommand(int src, String language) {
        if (language.length() != 3) {
            return null;
        }
        // Hdmi CEC uses lower-cased ISO 639-2 (3 letters code).
        String normalized = language.toLowerCase();
        byte[] params = new byte[] {
                (byte) normalized.charAt(0),
                (byte) normalized.charAt(1),
                (byte) normalized.charAt(2),
        };
        // <Set Menu Language> is broadcast message.
        return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_SET_MENU_LANGUAGE,
                params);
    }

    /**
     * Build &lt;Set Osd Name &gt; command.
     *
     * @param src source address of command
     * @param name display (OSD) name of device
     * @return newly created {@link HdmiCecMessage} if valid name. Otherwise,
     *         return null
     */
    static HdmiCecMessage buildSetOsdNameCommand(int src, int dest, String name) {
        int length = Math.min(name.length(), OSD_NAME_MAX_LENGTH);
        byte[] params;
        try {
            params = name.substring(0, length).getBytes("US-ASCII");
        } catch (UnsupportedEncodingException e) {
            return null;
        }
        return buildCommand(src, dest, HdmiCec.MESSAGE_SET_OSD_NAME, params);
    }

    /**
     * Build &lt;Report Physical Address&gt; command. It has two bytes physical
     * address and one byte device type as parameter.
     *
     * <p>This is a broadcast message sent to all devices on the bus.
     *
     * @param src source address of command
     * @param address physical address of device
     * @param deviceType type of device
     * @return newly created {@link HdmiCecMessage}
     */
    static HdmiCecMessage buildReportPhysicalAddressCommand(int src, int address, int deviceType) {
        byte[] params = new byte[] {
                // Two bytes for physical address
                (byte) ((address >> 8) & 0xFF),
                (byte) (address & 0xFF),
                // One byte device type
                (byte) deviceType
        };
        // <Report Physical Address> is broadcast message.
        return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS,
                params);
    }

    /**
     * Build &lt;Device Vendor Id&gt; command. It has three bytes vendor id as
     * parameter.
     *
     * <p>This is a broadcast message sent to all devices on the bus.
     *
     * @param src source address of command
     * @param vendorId device's vendor id
     * @return newly created {@link HdmiCecMessage}
     */
    static HdmiCecMessage buildDeviceVendorIdCommand(int src, int vendorId) {
        byte[] params = new byte[] {
                (byte) ((vendorId >> 16) & 0xFF),
                (byte) ((vendorId >> 8) & 0xFF),
                (byte) (vendorId & 0xFF)
        };
        // <Device Vendor Id> is broadcast message.
        return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_DEVICE_VENDOR_ID,
                params);
    }

    /**
     * Build &lt;Device Vendor Id&gt; command. It has one byte cec version as parameter.
     *
     * @param src source address of command
     * @param dest destination address of command
     * @param version version of cec. Use 0x04 for "Version 1.3a" and 0x05 for
     *                "Version 1.4 or 1.4a or 1.4b
     * @return newly created {@link HdmiCecMessage}
     */
    static HdmiCecMessage buildCecVersion(int src, int dest, int version) {
        byte[] params = new byte[] {
                (byte) version
        };
        return buildCommand(src, dest, HdmiCec.MESSAGE_CEC_VERSION, params);
    }

    /**
     * Build a {@link HdmiCecMessage} without extra parameter.
     *
     * @param src source address of command
     * @param dest destination address of command
     * @param opcode opcode for a message
     * @return newly created {@link HdmiCecMessage}
     */
    private static HdmiCecMessage buildCommand(int src, int dest, int opcode) {
        return new HdmiCecMessage(src, dest, opcode, HdmiCecMessage.EMPTY_PARAM);
    }

    /**
     * Build a {@link HdmiCecMessage} with given values.
     *
     * @param src source address of command
     * @param dest destination address of command
     * @param opcode opcode for a message
     * @param params extra parameters for command
     * @return newly created {@link HdmiCecMessage}
     */
    private static HdmiCecMessage buildCommand(int src, int dest, int opcode, byte[] params) {
        return new HdmiCecMessage(src, dest, opcode, params);
    }
}
+89 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.hdmi;

import android.annotation.Nullable;
import android.content.Context;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
import android.os.HandlerThread;
@@ -26,6 +27,8 @@ import android.util.Slog;

import com.android.server.SystemService;

import java.util.Locale;

/**
 * Provides a service for sending and processing HDMI control messages,
 * HDMI-CEC and MHL control command, and providing the information on both standard.
@@ -105,7 +108,7 @@ public final class HdmiControlService extends SystemService {
     * @param command CEC command to send out
     */
    void sendCecCommand(HdmiCecMessage command) {
        // TODO: Implement this.
        mCecController.sendCommand(command);
    }

    /**
@@ -116,4 +119,89 @@ public final class HdmiControlService extends SystemService {
    void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
        // TODO: Implement this.
    }

    boolean handleCecCommand(HdmiCecMessage message) {
        // Commands that queries system information replies directly instead
        // of creating FeatureAction because they are state-less.
        switch (message.getOpcode()) {
            case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
                handleGetMenuLanguage(message);
                return true;
            case HdmiCec.MESSAGE_GET_OSD_NAME:
                handleGetOsdName(message);
                return true;
            case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
                handleGivePhysicalAddress(message);
                return true;
            case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID:
                handleGiveDeviceVendorId(message);
                return true;
            case HdmiCec.MESSAGE_GET_CEC_VERSION:
                handleGetCecVersion(message);
                return true;
            // TODO: Add remaining system information query such as
            // <Give Device Power Status> and <Request Active Source> handler.
            default:
                Slog.w(TAG, "Unsupported cec command:" + message.toString());
                return false;
        }
    }

    private void handleGetCecVersion(HdmiCecMessage message) {
        int version = mCecController.getVersion();
        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
                message.getSource(),
                version);
        sendCecCommand(cecMessage);
    }

    private void handleGiveDeviceVendorId(HdmiCecMessage message) {
        int vendorId = mCecController.getVendorId();
        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
                message.getDestination(), vendorId);
        sendCecCommand(cecMessage);
    }

    private void handleGivePhysicalAddress(HdmiCecMessage message) {
        int physicalAddress = mCecController.getPhysicalAddress();
        int deviceType = HdmiCec.getTypeFromAddress(message.getDestination());
        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
                message.getDestination(), physicalAddress, deviceType);
        sendCecCommand(cecMessage);
    }

    private void handleGetOsdName(HdmiCecMessage message) {
        // TODO: read device name from settings or property.
        String name = HdmiCec.getDefaultDeviceName(message.getDestination());
        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
                message.getDestination(), message.getSource(), name);
        if (cecMessage != null) {
            sendCecCommand(cecMessage);
        } else {
            Slog.w(TAG, "Failed to build <Get Osd Name>:" + name);
        }
    }

    private void handleGetMenuLanguage(HdmiCecMessage message) {
        // Only 0 (TV), 14 (specific use) can answer.
        if (message.getDestination() != HdmiCec.ADDR_TV
                && message.getDestination() != HdmiCec.ADDR_SPECIFIC_USE) {
            Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
            sendCecCommand(
                    HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(),
                            message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE,
                            HdmiCecMessageBuilder.ABORT_UNRECOGNIZED_MODE));
            return;
        }

        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
                message.getDestination(),
                Locale.getDefault().getISO3Language());
        // TODO: figure out how to handle failed to get language code.
        if (command != null) {
            sendCecCommand(command);
        } else {
            Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
        }
    }
}
+4 −3
Original line number Diff line number Diff line
@@ -71,7 +71,8 @@ final class NewDeviceAction extends FeatureAction {
    @Override
    public boolean start() {
        sendCommand(
                buildCommand(mSourceAddress, mDeviceLogicalAddress, HdmiCec.MESSAGE_GET_OSD_NAME));
                HdmiCecMessageBuilder.buildGetOsdNameCommand(mSourceAddress,
                        mDeviceLogicalAddress));
        mState = STATE_WAITING_FOR_SET_OSD_NAME;
        addTimer(mState, TIMEOUT_MS);
        return true;
@@ -132,8 +133,8 @@ final class NewDeviceAction extends FeatureAction {
    }

    private void requestVendorId() {
        sendCommand(buildCommand(mSourceAddress, mDeviceLogicalAddress,
                HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID));
        sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress,
                mDeviceLogicalAddress));
        addTimer(mState, TIMEOUT_MS);
    }