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 bddb3f7fe4913..963e818d2cba6 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); } @@ -84,35 +98,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); } /** @@ -143,6 +205,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: @@ -151,6 +219,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(); + } +}