diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java index 296cc5b9d5178..d747cd00d6f4b 100644 --- a/services/core/java/com/android/server/hdmi/FeatureAction.java +++ b/services/core/java/com/android/server/hdmi/FeatureAction.java @@ -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) - }; - } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index b103a4dc29b48..5327ef43aaaa9 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -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 mDeviceInfos = new SparseArray(); + // Set-like container for all local devices' logical address. + // Key and value are same. + private final SparseArray mLocalLogicalAddresses = + new SparseArray(); // 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 { *

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 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); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java new file mode 100644 index 0000000000000..fc6183c75b67e --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -0,0 +1,212 @@ +/* + * 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 <Feature Abort> command. <Feature Abort> 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 <Get Osd Name> 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 <Give Vendor Id Command> 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 <Set Menu Language > command. + * + *

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), + }; + // is broadcast message. + return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_SET_MENU_LANGUAGE, + params); + } + + /** + * Build <Set Osd Name > 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 <Report Physical Address> command. It has two bytes physical + * address and one byte device type as parameter. + * + *

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 + }; + // is broadcast message. + return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS, + params); + } + + /** + * Build <Device Vendor Id> command. It has three bytes vendor id as + * parameter. + * + *

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) + }; + // is broadcast message. + return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_DEVICE_VENDOR_ID, + params); + } + + /** + * Build <Device Vendor Id> 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); + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index f99c71789204c..c1226459bbf7c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -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 + // and 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 :" + 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 :" + 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 : " + message.toString()); + } + } } diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java index c84a0672ef6d8..98da280878338 100644 --- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java +++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java @@ -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); }