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();
+ }
+}