From 67ea521d14f366fe5aac09e512865d31bfa0ee53 Mon Sep 17 00:00:00 2001 From: Jungshik Jang Date: Thu, 15 May 2014 14:05:24 +0900 Subject: [PATCH] Add several actions for ARC (Audio Return Channel) ARC channel is established by both TV and AV Reciever. From TV, it sends and AVR responds with . From AVR, it can be initiated by sending directly to TV. Once TV receives it sets up ARC internally and replies 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 () RequestArcAction handles common logic of them. SetArcTransmissionStateAction: handles ARC set up, enabling/disabling ARC and reports results to AVR. and 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 --- .../android/server/hdmi/FeatureAction.java | 27 +++- .../server/hdmi/HdmiCecController.java | 8 +- .../server/hdmi/HdmiCecMessageBuilder.java | 44 ++++++ .../server/hdmi/HdmiControlService.java | 120 ++++++++++++++-- .../android/server/hdmi/RequestArcAction.java | 100 ++++++++++++++ .../hdmi/RequestArcInitiationAction.java | 53 ++++++++ .../hdmi/RequestArcTerminationAction.java | 53 ++++++++ .../hdmi/SetArcTransmissionStateAction.java | 128 ++++++++++++++++++ 8 files changed, 516 insertions(+), 17 deletions(-) create mode 100644 services/core/java/com/android/server/hdmi/RequestArcAction.java create mode 100644 services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java create mode 100644 services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java create mode 100644 services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java index d747cd00d6f4b..1bc278dfe8b01 100644 --- a/services/core/java/com/android/server/hdmi/FeatureAction.java +++ b/services/core/java/com/android/server/hdmi/FeatureAction.java @@ -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. + * + *

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); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 986cb9b0db5b4..f99a01dccb39d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -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); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 6d2b83bef5878..e47b90da13739 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -185,6 +185,50 @@ public class HdmiCecMessageBuilder { return buildCommand(src, dest, HdmiCec.MESSAGE_CEC_VERSION, params); } + /** + * Build <Request Arc Initiation> + * + * @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 <Request Arc Termination> + * + * @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 <Report Arc Initiated> + * + * @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 <Report Arc Terminated> + * + * @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. * diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 7c136db833c45..9719569554366 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -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 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 { *

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 void removeAction(final Class clazz) { + runOnServiceThread(new Runnable() { + @Override + public void run() { + Iterator 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 // and 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 is started by + // 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 is started by + // 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(), diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java new file mode 100644 index 0000000000000..196f28aaabf16 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java @@ -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 <Request ARC Initiation>/<Request ARC Termination>. + */ +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 here and, both and + // are handled in HdmiControlService itself because both can be + // received wihtout or . + 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(); + } +} diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java new file mode 100644 index 0000000000000..0a701f91ba3f5 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java @@ -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. + * + *

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 "); + // If failed to send , start "Disabled" ARC transmission + // action. + disableArcTransmission(); + finish(); + } + return true; + } +} diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java new file mode 100644 index 0000000000000..db1b992fd2114 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java @@ -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 . + * + *

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 "); + // If failed to send , start "Disabled" ARC transmission + // action. + disableArcTransmission(); + finish(); + } + return true; + } +} diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java new file mode 100644 index 0000000000000..6bf149b09674c --- /dev/null +++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java @@ -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 <Initiate ARC>, TV sends <Report ARC Initiated> to AV Receiver. + * If it fails or it gets <Terminate ARC>, TV just disables ARC. + */ +final class SetArcTransmissionStateAction extends FeatureAction { + private static final String TAG = "SetArcTransmissionStateAction"; + + // State in which the action sent and + // is waiting for time out. If it receives 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 . + // If AVR responds with , disable ARC status again. + // This is different from spec that says that turns ARC status to "Enabled" + // if is acknowledged and no is received. + // But implemented this way to save the time having to wait for . + setArcStatus(true); + // If succeeds to send , wait general timeout + // to check whether there is no for . + mState = STATE_WAITING_TIMEOUT; + addTimer(mState, TIMEOUT_MS); + } else { + // If fails to send , disable ARC and + // send directly. + Slog.w(TAG, "Failed to send :[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 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 . + finish(); + } +}