Merge "Implement handlers for system information query command."

This commit is contained in:
Jungshik Jang
2014-05-15 02:30:48 +00:00
committed by Android (Google) Code Review
5 changed files with 333 additions and 57 deletions

View File

@@ -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)
};
}
}

View File

@@ -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);
}

View File

@@ -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 &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);
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}