Implement handlers for system information query command.
System information query commands are stateless command and
it should be replied immediately when it receives query command.
Here is a list of command handler (and it's reply) implemented
in this change.
1. <Get Menu Language> -> <Set Menu Language>
2. <Give(Get) OSD Name> -> <Set OSD Name>
3. <Give Physical Address> -> <Report Physical Address>
4. <Give Device Vendor ID> -> <Device Vendor Id>
5. <Give CEC Version> -> <CEC Version>
In order to centralize all cec message building, added new
builder class, HdmiCecMessageBuilder for HdmiCecMessage.
Accordingly, all helper methods for building of HdmiCecMessage
are moved to HdmiCecMessageBuilder class.
Change-Id: Ib7c5d2b0cb3d69d51159af8bc277ffb49a60909b
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index 296cc5b..d747cd0 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 @@
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 @@
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 b103a4d..5327ef4 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 @@
// 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 @@
// 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 @@
*/
int addLogicalAddress(int newLogicalAddress) {
if (HdmiCec.isValidAddress(newLogicalAddress)) {
+ mLocalLogicalAddresses.append(newLogicalAddress, newLogicalAddress);
return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
} else {
return -1;
@@ -337,6 +335,9 @@
* <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 @@
}
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);
}
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 0000000..fc6183c
--- /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.
+ *
+ * <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 <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.
+ *
+ * <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 <Device Vendor Id> 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 <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 f99c717..c122645 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 @@
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 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 @@
* @param command CEC command to send out
*/
void sendCecCommand(HdmiCecMessage command) {
- // TODO: Implement this.
+ mCecController.sendCommand(command);
}
/**
@@ -116,4 +119,89 @@
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());
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index c84a067..98da280 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 @@
@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 @@
}
private void requestVendorId() {
- sendCommand(buildCommand(mSourceAddress, mDeviceLogicalAddress,
- HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID));
+ sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress,
+ mDeviceLogicalAddress));
addTimer(mState, TIMEOUT_MS);
}