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:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user