Add SendMessageCallback to Hdmi control service.

As we have separate IO thread, we should have async callback
mechanism to get result of send request.
For that, I added SendMessageCallback interface to HdmiControl
Service
Along with this, replace message-based IO handling with
post Runnable based one for consistency

Change-Id: I61cf5b751b4f2af3b34956060869f3512f161d11
This commit is contained in:
Jungshik Jang
2014-05-22 19:28:09 +09:00
parent 521d8cdae0
commit d643f764f7
6 changed files with 190 additions and 171 deletions

View File

@@ -164,8 +164,13 @@ abstract class FeatureAction {
mActionTimer.sendTimerMessage(state, delayMillis);
}
protected final boolean sendCommand(HdmiCecMessage cmd) {
return mService.sendCecCommand(cmd);
protected final void sendCommand(HdmiCecMessage cmd) {
mService.sendCecCommand(cmd);
}
protected final void sendCommand(HdmiCecMessage cmd,
HdmiControlService.SendMessageCallback callback) {
mService.sendCecCommand(cmd, callback);
}
/**

View File

@@ -20,9 +20,6 @@ import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -176,13 +173,59 @@ final class HdmiCecController {
* Otherwise, scan address will start from {@code preferredAddress}
* @param callback callback interface to report allocated logical address to caller
*/
void allocateLogicalAddress(int deviceType, int preferredAddress,
AllocateLogicalAddressCallback callback) {
Message msg = mIoHandler.obtainMessage(MSG_ALLOCATE_LOGICAL_ADDRESS);
msg.arg1 = deviceType;
msg.arg2 = preferredAddress;
msg.obj = callback;
mIoHandler.sendMessage(msg);
void allocateLogicalAddress(final int deviceType, final int preferredAddress,
final AllocateLogicalAddressCallback callback) {
runOnIoThread(new Runnable() {
@Override
public void run() {
handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
}
});
}
private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
final AllocateLogicalAddressCallback callback) {
int startAddress = preferredAddress;
// If preferred address is "unregistered", start address will be the smallest
// address matched with the given device type.
if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
if (deviceType == HdmiCec.getTypeFromAddress(i)) {
startAddress = i;
break;
}
}
}
int logicalAddress = HdmiCec.ADDR_UNREGISTERED;
// Iterates all possible addresses which has the same device type.
for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
if (curAddress != HdmiCec.ADDR_UNREGISTERED
&& deviceType == HdmiCec.getTypeFromAddress(i)) {
// <Polling Message> is a message which has empty body and
// uses same address for both source and destination address.
// If sending <Polling Message> failed (NAK), it becomes
// new logical address for the device because no device uses
// it as logical address of the device.
int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress,
EMPTY_BODY);
if (error != ERROR_SUCCESS) {
logicalAddress = curAddress;
break;
}
}
}
final int assignedAddress = logicalAddress;
if (callback != null) {
runOnServiceThread(new Runnable() {
@Override
public void run() {
callback.onAllocated(deviceType, assignedAddress);
}
});
}
}
private static byte[] buildBody(int opcode, byte[] params) {
@@ -192,101 +235,6 @@ final class HdmiCecController {
return body;
}
private final class IoHandler extends Handler {
private IoHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SEND_CEC_COMMAND:
HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj;
byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
cecMessage.getDestination(), body);
break;
case MSG_ALLOCATE_LOGICAL_ADDRESS:
int deviceType = msg.arg1;
int preferredAddress = msg.arg2;
AllocateLogicalAddressCallback callback =
(AllocateLogicalAddressCallback) msg.obj;
handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
break;
default:
Slog.w(TAG, "Unsupported CEC Io request:" + msg.what);
break;
}
}
private void handleAllocateLogicalAddress(int deviceType, int preferredAddress,
AllocateLogicalAddressCallback callback) {
int startAddress = preferredAddress;
// If preferred address is "unregistered", start_index will be the smallest
// address matched with the given device type.
if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
if (deviceType == HdmiCec.getTypeFromAddress(i)) {
startAddress = i;
break;
}
}
}
int logcialAddress = HdmiCec.ADDR_UNREGISTERED;
// Iterates all possible addresses which has the same device type.
for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
if (curAddress != HdmiCec.ADDR_UNREGISTERED
&& deviceType == HdmiCec.getTypeFromAddress(i)) {
// <Polling Message> is a message which has empty body and
// uses same address for both source and destination address.
// If sending <Polling Message> failed (NAK), it becomes
// new logical address for the device because no device uses
// it as logical address of the device.
int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress,
EMPTY_BODY);
if (error != ERROR_SUCCESS) {
logcialAddress = curAddress;
break;
}
}
}
Message msg = mControlHandler.obtainMessage(MSG_REPORT_LOGICAL_ADDRESS);
msg.arg1 = deviceType;
msg.arg2 = logcialAddress;
msg.obj = callback;
mControlHandler.sendMessage(msg);
}
}
private final class ControlHandler extends Handler {
private ControlHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_RECEIVE_CEC_COMMAND:
// TODO: delegate it to HdmiControl service.
onReceiveCommand((HdmiCecMessage) msg.obj);
break;
case MSG_REPORT_LOGICAL_ADDRESS:
int deviceType = msg.arg1;
int logicalAddress = msg.arg2;
AllocateLogicalAddressCallback callback =
(AllocateLogicalAddressCallback) msg.obj;
callback.onAllocated(deviceType, logicalAddress);
break;
default:
Slog.i(TAG, "Unsupported message type:" + msg.what);
break;
}
}
}
/**
* Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
* logical address as new device info's.
@@ -411,10 +359,18 @@ final class HdmiCecController {
return nativeGetVendorId(mNativePtr);
}
private void runOnIoThread(Runnable runnable) {
mIoHandler.post(runnable);
}
private void runOnServiceThread(Runnable runnable) {
mControlHandler.post(runnable);
}
private void init(HdmiControlService service, long nativePtr) {
mService = service;
mIoHandler = new IoHandler(service.getServiceLooper());
mControlHandler = new ControlHandler(service.getServiceLooper());
mIoHandler = new Handler(service.getServiceLooper());
mControlHandler = new Handler(service.getServiceLooper());
mNativePtr = nativePtr;
}
@@ -438,13 +394,27 @@ final class HdmiCecController {
HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand
(sourceAddress, message.getSource(), message.getOpcode(),
HdmiCecMessageBuilder.ABORT_REFUSED);
sendCommand(cecMessage);
sendCommand(cecMessage, null);
}
boolean sendCommand(HdmiCecMessage cecMessage) {
Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
return mIoHandler.sendMessage(message);
void sendCommand(final HdmiCecMessage cecMessage,
final HdmiControlService.SendMessageCallback callback) {
runOnIoThread(new Runnable() {
@Override
public void run() {
byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
cecMessage.getDestination(), body);
if (callback != null) {
runOnServiceThread(new Runnable() {
@Override
public void run() {
callback.onSendCompleted(error);
}
});
}
}
});
}
/**
@@ -453,12 +423,15 @@ final class HdmiCecController {
private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
byte opcode = body[0];
byte params[] = Arrays.copyOfRange(body, 1, body.length);
HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
final HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
// Delegate message to main handler so that it handles in main thread.
Message message = mControlHandler.obtainMessage(
MSG_RECEIVE_CEC_COMMAND, cecMessage);
mControlHandler.sendMessage(message);
runOnServiceThread(new Runnable() {
@Override
public void run() {
onReceiveCommand(cecMessage);
}
});
}
/**

View File

@@ -18,30 +18,25 @@ package com.android.server.hdmi;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.IHdmiControlService;
import android.hardware.hdmi.IHdmiHotplugEventListener;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.IHdmiControlService;
import android.hardware.hdmi.IHdmiHotplugEventListener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
/**
@@ -54,6 +49,20 @@ public final class HdmiControlService extends SystemService {
// TODO: Rename the permission to HDMI_CONTROL.
private static final String PERMISSION = "android.permission.HDMI_CEC";
/**
* Interface to report send result.
*/
interface SendMessageCallback {
/**
* Called when {@link HdmiControlService#sendCecCommand} is completed.
*
* @param error result of send request. 0 if succeed. Otherwise it will be
* negative value
*/
// TODO: define error code as constants and update javadoc.
void onSendCompleted(int error);
}
// A thread to handle synchronous IO of CEC and MHL control service.
// Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
// and sparse call it shares a thread to handle IO operations.
@@ -205,10 +214,14 @@ public final class HdmiControlService extends SystemService {
* Transmit a CEC command to CEC bus.
*
* @param command CEC command to send out
* @return {@code true} if succeeds to send command
* @param callback interface used to the result of send command
*/
boolean sendCecCommand(HdmiCecMessage command) {
return mCecController.sendCommand(command);
void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
mCecController.sendCommand(command, callback);
}
void sendCecCommand(HdmiCecMessage command) {
mCecController.sendCommand(command, null);
}
/**

View File

@@ -16,6 +16,7 @@
package com.android.server.hdmi;
import android.hardware.hdmi.HdmiCecMessage;
import android.util.Slog;
/**
@@ -37,17 +38,24 @@ final class RequestArcInitiationAction extends RequestArcAction {
@Override
boolean start() {
if (sendCommand(
HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress, mAvrAddress))) {
mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
addTimer(mState, TIMEOUT_MS);
} else {
Slog.w(TAG, "Failed to send <Request ARC Initiation>");
// If failed to send <Request ARC Initiation>, start "Disabled" ARC transmission
// action.
disableArcTransmission();
finish();
}
HdmiCecMessage command = HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress,
mAvrAddress);
sendCommand(command, new HdmiControlService.SendMessageCallback() {
@Override
public void onSendCompleted(int error) {
// success.
if (error == 0) {
mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
addTimer(mState, TIMEOUT_MS);
} else {
Slog.w(TAG, "Failed to send <Request ARC Initiation>");
// If failed to send <Request ARC Initiation>, start "Disabled"
// ARC transmission action.
disableArcTransmission();
finish();
}
}
});
return true;
}
}

View File

@@ -16,6 +16,7 @@
package com.android.server.hdmi;
import android.hardware.hdmi.HdmiCecMessage;
import android.util.Slog;
/**
@@ -37,17 +38,23 @@ final class RequestArcTerminationAction extends RequestArcAction {
@Override
boolean start() {
if (sendCommand(
HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress))) {
mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
addTimer(mState, TIMEOUT_MS);
} else {
Slog.w(TAG, "Failed to send <Request ARC Initiation>");
// If failed to send <Request ARC Termination>, start "Disabled" ARC transmission
// action.
disableArcTransmission();
finish();
}
HdmiCecMessage command =
HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress);
sendCommand(command, new HdmiControlService.SendMessageCallback() {
@Override
public void onSendCompleted(int error) {
if (error == 0) {
mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
addTimer(mState, TIMEOUT_MS);
} else {
Slog.w(TAG, "Failed to send <Request ARC Initiation>");
// If failed to send <Request ARC Termination>, start "Disabled" ARC
// transmission action.
disableArcTransmission();
finish();
}
}
});
return true;
}
}

View File

@@ -64,26 +64,7 @@ final class SetArcTransmissionStateAction extends FeatureAction {
@Override
boolean start() {
if (mEnabled) {
if (sendCommand(
HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress))) {
// Enable ARC status immediately after sending <Report Arc Initiated>.
// If AVR responds with <Feature Abort>, disable ARC status again.
// This is different from spec that says that turns ARC status to "Enabled"
// if <Report ARC Initiated> is acknowledged and no <Feature Abort> is received.
// But implemented this way to save the time having to wait for <Feature Abort>.
setArcStatus(true);
// If succeeds to send <Report ARC Initiated>, wait general timeout
// to check whether there is no <Feature Abort> for <Report ARC Initiated>.
mState = STATE_WAITING_TIMEOUT;
addTimer(mState, TIMEOUT_MS);
} else {
// If fails to send <Report ARC Initiated>, disable ARC and
// send <Report ARC Terminated> directly.
Slog.w(TAG, "Failed to send <Report ARC Initiated>:[source:" + mSourceAddress
+ ", avr Address:" + mAvrAddress + "]");
setArcStatus(false);
finish();
}
sendReportArcInitiated();
} else {
setArcStatus(false);
finish();
@@ -91,6 +72,38 @@ final class SetArcTransmissionStateAction extends FeatureAction {
return true;
}
private void sendReportArcInitiated() {
HdmiCecMessage command =
HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress);
sendCommand(command, new HdmiControlService.SendMessageCallback() {
@Override
public void onSendCompleted(int error) {
if (error == 0) {
// Enable ARC status immediately after sending <Report Arc Initiated>.
// If AVR responds with <Feature Abort>, disable ARC status again.
// This is different from spec that says that turns ARC status to
// "Enabled" if <Report ARC Initiated> is acknowledged and no
// <Feature Abort> is received.
// But implemented this way to save the time having to wait for
// <Feature Abort>.
setArcStatus(true);
// If succeeds to send <Report ARC Initiated>, wait general timeout
// to check whether there is no <Feature Abort> for <Report ARC Initiated>.
mState = STATE_WAITING_TIMEOUT;
addTimer(mState, TIMEOUT_MS);
} else {
// If fails to send <Report ARC Initiated>, disable ARC and
// send <Report ARC Terminated> directly.
Slog.w(TAG, "Failed to send <Report ARC Initiated>:[source:"
+ mSourceAddress
+ ", avr Address:" + mAvrAddress + "]");
setArcStatus(false);
finish();
}
}
});
}
private void setArcStatus(boolean enabled) {
boolean wasEnabled = mService.setArcStatus(enabled);
Slog.i(TAG, "Change arc status [old:" + wasEnabled + " ,new:" + enabled);