Add several actions for ARC (Audio Return Channel)

ARC channel is established by both TV and AV Reciever.
From TV, it sends <Request ARC Initiation> and AVR
responds with <Initiate ARC>.
From AVR, it can be initiated by sending <Initiate ARC> directly
to TV.
Once TV receives <Initiate ARC> it sets up ARC internally
and replies <Report ARC Initiated> to AVR.
Termination steps are almost same except for message name
(use Terminate instread of Initiation).

In order to implement the above steps, this change introduces
following classes.

RequestArcInitiation(Termination)Action:
  handles <Request ARC Initiation> (<Request ARC Termination>)
  RequestArcAction handles common logic of them.

SetArcTransmissionStateAction:
  handles ARC set up, enabling/disabling ARC and
  reports results to AVR.

<Initiate ARC> and <Terminate ARC> handles directly in
HdmiControlService

Along with this, this change has implmentation for
add&removeAction. To avoid synchronization issue
they are isolated to main thread.

Change-Id: I3c5cf7c777e6c1de50d63ce4643b191dfe15fe1f
This commit is contained in:
Jungshik Jang
2014-05-15 14:05:24 +09:00
parent 193909da71
commit 67ea521d14
8 changed files with 516 additions and 17 deletions

View File

@@ -118,6 +118,11 @@ abstract class FeatureAction {
* @param delayMillis amount of delay for the timer
*/
void sendTimerMessage(int state, long delayMillis);
/**
* Removes any pending timer message.
*/
void clearTimerMessage();
}
private class ActionTimerHandler extends Handler implements ActionTimer {
@@ -131,6 +136,11 @@ abstract class FeatureAction {
sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state), delayMillis);
}
@Override
public void clearTimerMessage() {
removeMessages(MSG_TIMEOUT);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -154,15 +164,26 @@ abstract class FeatureAction {
mActionTimer.sendTimerMessage(state, delayMillis);
}
protected final void sendCommand(HdmiCecMessage cmd) {
mService.sendCecCommand(cmd);
protected final boolean sendCommand(HdmiCecMessage cmd) {
return mService.sendCecCommand(cmd);
}
/**
* Clean up action's state.
*
* <p>Declared as package-private. Only {@link HdmiControlService} can access it.
*/
void clear() {
mState = STATE_NONE;
// Clear all timers.
mActionTimer.clearTimerMessage();
}
/**
* Finish up the action. Reset the state, and remove itself from the action queue.
*/
protected void finish() {
mState = STATE_NONE;
clear();
removeAction(this);
}

View File

@@ -442,9 +442,9 @@ final class HdmiCecController {
}
void sendCommand(HdmiCecMessage cecMessage) {
boolean sendCommand(HdmiCecMessage cecMessage) {
Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
mIoHandler.sendMessage(message);
return mIoHandler.sendMessage(message);
}
/**
@@ -465,7 +465,9 @@ final class HdmiCecController {
* Called by native when a hotplug event issues.
*/
private void handleHotplug(boolean connected) {
// TODO: Delegate event to main message handler.
// TODO: once add port number to cec HAL interface, pass port number
// to the service.
mService.onHotplug(0, connected);
}
private static native long nativeInit(HdmiCecController handler);

View File

@@ -185,6 +185,50 @@ public class HdmiCecMessageBuilder {
return buildCommand(src, dest, HdmiCec.MESSAGE_CEC_VERSION, params);
}
/**
* Build &lt;Request Arc Initiation&gt;
*
* @param src source address of command
* @param dest destination address of command
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildRequestArcInitiation(int src, int dest) {
return buildCommand(src, dest, HdmiCec.MESSAGE_REQUEST_ARC_INITIATION);
}
/**
* Build &lt;Request Arc Termination&gt;
*
* @param src source address of command
* @param dest destination address of command
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildRequestArcTermination(int src, int dest) {
return buildCommand(src, dest, HdmiCec.MESSAGE_REQUEST_ARC_TERMINATION);
}
/**
* Build &lt;Report Arc Initiated&gt;
*
* @param src source address of command
* @param dest destination address of command
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildReportArcInitiated(int src, int dest) {
return buildCommand(src, dest, HdmiCec.MESSAGE_REPORT_ARC_INITIATED);
}
/**
* Build &lt;Report Arc Terminated&gt;
*
* @param src source address of command
* @param dest destination address of command
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildReportArcTerminated(int src, int dest) {
return buildCommand(src, dest, HdmiCec.MESSAGE_REPORT_ARC_TERMINATED);
}
/**
* Build a {@link HdmiCecMessage} without extra parameter.
*

View File

@@ -21,12 +21,15 @@ import android.content.Context;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Slog;
import com.android.server.SystemService;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
/**
@@ -41,12 +44,23 @@ public final class HdmiControlService extends SystemService {
// and sparse call it shares a thread to handle IO operations.
private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
// A collection of FeatureAction.
// Note that access to this collection should happen in service thread.
private final LinkedList<FeatureAction> mActions = new LinkedList<>();
@Nullable
private HdmiCecController mCecController;
@Nullable
private HdmiMhlController mMhlController;
// Whether ARC is "enabled" or not.
// TODO: it may need to hold lock if it's accessed from others.
private boolean mArcStatusEnabled = false;
// Handler running on service thread. It's used to run a task in service thread.
private Handler mHandler = new Handler();
public HdmiControlService(Context context) {
super(context);
}
@@ -83,35 +97,83 @@ public final class HdmiControlService extends SystemService {
* <p>Declared as package-private.
*/
Looper getServiceLooper() {
return Looper.myLooper();
return mHandler.getLooper();
}
/**
* Add a new {@link FeatureAction} to the action queue.
* Add and start a new {@link FeatureAction} to the action queue.
*
* @param action {@link FeatureAction} to add
* @param action {@link FeatureAction} to add and start
*/
void addAction(FeatureAction action) {
// TODO: Implement this.
void addAndStartAction(final FeatureAction action) {
// TODO: may need to check the number of stale actions.
runOnServiceThread(new Runnable() {
@Override
public void run() {
mActions.add(action);
action.start();
}
});
}
/**
* Remove the given {@link FeatureAction} object from the action queue.
*
* @param action {@link FeatureAction} to add
* @param action {@link FeatureAction} to remove
*/
void removeAction(FeatureAction action) {
// TODO: Implement this.
void removeAction(final FeatureAction action) {
runOnServiceThread(new Runnable() {
@Override
public void run() {
mActions.remove(action);
}
});
}
// Remove all actions matched with the given Class type.
private <T extends FeatureAction> void removeAction(final Class<T> clazz) {
runOnServiceThread(new Runnable() {
@Override
public void run() {
Iterator<FeatureAction> iter = mActions.iterator();
while (iter.hasNext()) {
FeatureAction action = iter.next();
if (action.getClass().equals(clazz)) {
action.clear();
mActions.remove(action);
}
}
}
});
}
private void runOnServiceThread(Runnable runnable) {
mHandler.post(runnable);
}
/**
* Change ARC status into the given {@code enabled} status.
*
* @return {@code true} if ARC was in "Enabled" status
*/
boolean setArcStatus(boolean enabled) {
boolean oldStatus = mArcStatusEnabled;
// 1. Enable/disable ARC circuit.
// TODO: call set_audio_return_channel of hal interface.
// 2. Update arc status;
mArcStatusEnabled = enabled;
return oldStatus;
}
/**
* Transmit a CEC command to CEC bus.
*
* @param command CEC command to send out
* @return {@code true} if succeeds to send command
*/
void sendCecCommand(HdmiCecMessage command) {
mCecController.sendCommand(command);
boolean sendCecCommand(HdmiCecMessage command) {
return mCecController.sendCommand(command);
}
/**
@@ -142,6 +204,12 @@ public final class HdmiControlService extends SystemService {
case HdmiCec.MESSAGE_GET_CEC_VERSION:
handleGetCecVersion(message);
return true;
case HdmiCec.MESSAGE_INITIATE_ARC:
handleInitiateArc(message);
return true;
case HdmiCec.MESSAGE_TERMINATE_ARC:
handleTerminateArc(message);
return true;
// TODO: Add remaining system information query such as
// <Give Device Power Status> and <Request Active Source> handler.
default:
@@ -150,6 +218,36 @@ public final class HdmiControlService extends SystemService {
}
}
/**
* Called when a new hotplug event is issued.
*
* @param port hdmi port number where hot plug event issued.
* @param connected whether to be plugged in or not
*/
void onHotplug(int portNo, boolean connected) {
// TODO: Start "RequestArcInitiationAction" if ARC port.
}
private void handleInitiateArc(HdmiCecMessage message){
// In case where <Initiate Arc> is started by <Request ARC Initiation>
// need to clean up RequestArcInitiationAction.
removeAction(RequestArcInitiationAction.class);
SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
message.getDestination(), message.getSource(), true);
addAndStartAction(action);
}
private void handleTerminateArc(HdmiCecMessage message) {
// In case where <Terminate Arc> is started by <Request ARC Termination>
// need to clean up RequestArcInitiationAction.
// TODO: check conditions of power status by calling is_connected api
// to be added soon.
removeAction(RequestArcTerminationAction.class);
SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
message.getDestination(), message.getSource(), false);
addAndStartAction(action);
}
private void handleGetCecVersion(HdmiCecMessage message) {
int version = mCecController.getVersion();
HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),

View File

@@ -0,0 +1,100 @@
/*
* 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 android.util.Slog;
/**
* Base feature action class for &lt;Request ARC Initiation&gt;/&lt;Request ARC Termination&gt;.
*/
abstract class RequestArcAction extends FeatureAction {
private static final String TAG = "RequestArcAction";
// State in which waits for ARC response.
protected static final int STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE = 1;
// Logical address of AV Receiver.
protected final int mAvrAddress;
/**
* @Constructor
*
* @param service {@link HdmiControlService} instance
* @param sourceAddress logical address to be used as source address. It should
* TV type
* @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type
* @throw IllegalArugmentException if device type of sourceAddress and avrAddress
* is invalid
*/
RequestArcAction(HdmiControlService service, int sourceAddress, int avrAddress) {
super(service, sourceAddress);
verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV);
verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
mAvrAddress = avrAddress;
}
private static void verifyAddressType(int logicalAddress, int deviceType) {
int actualDeviceType = HdmiCec.getTypeFromAddress(logicalAddress);
if (actualDeviceType != deviceType) {
throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType
+ ", Actual:" + actualDeviceType);
}
}
@Override
boolean processCommand(HdmiCecMessage cmd) {
if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE) {
return false;
}
int src = cmd.getSource();
if (src != mAvrAddress) {
Slog.w(TAG, "Invalid source [Expected:" + mAvrAddress + ", Actual:" + src + "]");
return false;
}
int opcode = cmd.getOpcode();
switch (opcode) {
// Handles only <Feature Abort> here and, both <Initiate ARC> and <Terminate ARC>
// are handled in HdmiControlService itself because both can be
// received wihtout <Request ARC Initiation> or <Request ARC Termination>.
case HdmiCec.MESSAGE_FEATURE_ABORT:
disableArcTransmission();
finish();
return true;
default:
Slog.w(TAG, "Unsupported opcode:" + cmd.toString());
}
return false;
}
protected final void disableArcTransmission() {
// Start Set ARC Transmission State action.
SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(mService,
mSourceAddress, mAvrAddress, false);
mService.addAndStartAction(action);
}
@Override
final void handleTimerEvent(int state) {
if (mState != state || state != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE) {
return;
}
disableArcTransmission();
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.util.Slog;
/**
* Feature action that handles ARC action initiated by TV devices.
*
* <p>This action is created by TV's hot plug event of ARC port.
*/
final class RequestArcInitiationAction extends RequestArcAction {
private static final String TAG = "RequestArcInitiationAction";
/**
* @Constructor
*
* For more details look at {@link RequestArcAction#RequestArcAction}.
*/
RequestArcInitiationAction(HdmiControlService service, int sourceAddress, int avrAddress) {
super(service, sourceAddress, avrAddress);
}
@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();
}
return true;
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.util.Slog;
/**
* Feature action to handle <Request ARC Termination>.
*
* <p>It's initiated by user's manual termination or ARC channel close from TV.
*/
final class RequestArcTerminationAction extends RequestArcAction {
private static final String TAG = "RequestArcTerminationAction";
/**
* @Constructor
*
* @see RequestArcAction#RequestArcAction
*/
RequestArcTerminationAction(HdmiControlService service, int sourceAddress, int avrAddress) {
super(service, sourceAddress, avrAddress);
}
@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();
}
return true;
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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 android.util.Slog;
/**
* Feature action that handles enabling/disabling of ARC transmission channel.
* Once TV gets &lt;Initiate ARC&gt;, TV sends &lt;Report ARC Initiated&gt; to AV Receiver.
* If it fails or it gets &lt;Terminate ARC&gt;, TV just disables ARC.
*/
final class SetArcTransmissionStateAction extends FeatureAction {
private static final String TAG = "SetArcTransmissionStateAction";
// State in which the action sent <Rerpot Arc Initiated> and
// is waiting for time out. If it receives <Feature Abort> within timeout
// ARC should be disabled.
private static final int STATE_WAITING_TIMEOUT = 1;
private final boolean mEnabled;
private final int mAvrAddress;
/**
* @Constructor
*
* @param service an instance of {@link HdmiControlService}
* @param sourceAddress logical address to be used as source address
* @param enabled whether to enable ARC Transmission channel
*/
SetArcTransmissionStateAction(HdmiControlService service, int sourceAddress, int avrAddress,
boolean enabled) {
super(service, sourceAddress);
verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV);
verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
mAvrAddress = avrAddress;
mEnabled = enabled;
}
// TODO: extract it as separate utility class.
private static void verifyAddressType(int logicalAddress, int deviceType) {
int actualDeviceType = HdmiCec.getTypeFromAddress(logicalAddress);
if (actualDeviceType != deviceType) {
throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType
+ ", Actual:" + actualDeviceType);
}
}
@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();
}
} else {
setArcStatus(false);
finish();
}
return true;
}
private void setArcStatus(boolean enabled) {
boolean wasEnabled = mService.setArcStatus(enabled);
Slog.i(TAG, "Change arc status [old:" + wasEnabled + " ,new:" + enabled);
// If enabled before and set to "disabled" and send <Report Arc Terminated> to
// av reciever.
if (!enabled && wasEnabled) {
sendCommand(
HdmiCecMessageBuilder.buildReportArcTerminated(mSourceAddress, mAvrAddress));
}
}
@Override
boolean processCommand(HdmiCecMessage cmd) {
if (mState != STATE_WAITING_TIMEOUT) {
return false;
}
int opcode = cmd.getOpcode();
if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) {
setArcStatus(false);
}
finish();
return true;
}
@Override
void handleTimerEvent(int state) {
if (mState != state || mState != STATE_WAITING_TIMEOUT) {
return;
}
// Expire timeout for <Feature Abort>.
finish();
}
}