diff options
| author | 2014-05-04 21:51:42 +0000 | |
|---|---|---|
| committer | 2014-05-04 21:51:44 +0000 | |
| commit | 043aa252cf67ebe5ef9abe34e8922098a995272c (patch) | |
| tree | a7dd5c3c99d6086e42c60567fbd79b130c7d8ede | |
| parent | c74b3cb6f057da60dce6e99547a668e57aa478aa (diff) | |
| parent | c70d2295dd3fb87ce8c81c704688d1ad05043b4d (diff) | |
Merge "Define Feature action for obtaining info on a new CEC device"
5 files changed, 421 insertions, 0 deletions
diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java index eafaed67ba03..7213c78fbc89 100644 --- a/core/java/android/hardware/hdmi/HdmiCec.java +++ b/core/java/android/hardware/hdmi/HdmiCec.java @@ -160,6 +160,8 @@ public final class HdmiCec { public static final int MESSAGE_SET_EXTERNAL_TIMER = 0xA2; public static final int MESSAGE_ABORT = 0xFF; + public static final int UNKNOWN_VENDOR_ID = 0xFFFFFF; + public static final int POWER_STATUS_UNKNOWN = -1; public static final int POWER_STATUS_ON = 0; public static final int POWER_STATUS_STANDBY = 1; diff --git a/core/java/android/hardware/hdmi/HdmiCecMessage.java b/core/java/android/hardware/hdmi/HdmiCecMessage.java index be94d971b8fe..ddaf870b0fc7 100644 --- a/core/java/android/hardware/hdmi/HdmiCecMessage.java +++ b/core/java/android/hardware/hdmi/HdmiCecMessage.java @@ -19,6 +19,8 @@ package android.hardware.hdmi; import android.os.Parcel; import android.os.Parcelable; +import libcore.util.EmptyArray; + import java.util.Arrays; /** @@ -28,6 +30,8 @@ import java.util.Arrays; */ public final class HdmiCecMessage implements Parcelable { + public static final byte[] EMPTY_PARAM = EmptyArray.BYTE; + private static final int MAX_MESSAGE_LENGTH = 16; private final int mSource; diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java new file mode 100644 index 000000000000..296cc5b9d517 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/FeatureAction.java @@ -0,0 +1,211 @@ +/* + * 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.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Encapsulates a sequence of CEC/MHL command exchange for a certain feature. + * + * <p>Many CEC/MHL features are accomplished by CEC devices on the bus exchanging + * more than one command. {@link FeatureAction} represents the life cycle of the communication, + * manages the state as the process progresses, and if necessary, returns the result + * to the caller which initiates the action, through the callback given at the creation + * of the object. All the actual action classes inherit FeatureAction. + * + * <p>More than one FeatureAction objects can be up and running simultaneously, + * maintained by {@link HdmiControlService}. Each action is passed a new command + * arriving from the bus, and either consumes it if the command is what the action expects, + * or yields it to other action. + * + * Declared as package private, accessed by {@link HdmiControlService} only. + */ +abstract class FeatureAction { + + private static final String TAG = "FeatureAction"; + + // Timer handler message used for timeout event + protected static final int MSG_TIMEOUT = 100; + + // Default timeout for the incoming command to arrive in response to a request + protected static final int TIMEOUT_MS = 1000; + + // Default state used in common by all the feature actions. + protected static final int STATE_NONE = 0; + + // Internal state indicating the progress of action. + protected int mState = STATE_NONE; + + protected final HdmiControlService mService; + + // Logical address of the device for which the feature action is taken. The commands + // generated in an action all use this field as source address. + protected final int mSourceAddress; + + // Timer that manages timeout events. + protected ActionTimer mActionTimer; + + FeatureAction(HdmiControlService service, int sourceAddress) { + mService = service; + mSourceAddress = sourceAddress; + mActionTimer = createActionTimer(service.getServiceLooper()); + } + + @VisibleForTesting + void setActionTimer(ActionTimer actionTimer) { + mActionTimer = actionTimer; + } + + /** + * Called right after the action is created. Initialization or first step to take + * for the action can be done in this method. + * + * @return true if the operation is successful; otherwise false. + */ + abstract boolean start(); + + /** + * Process the command. Called whenever a new command arrives. + * + * @param cmd command to process + * @return true if the command was consumed in the process; Otherwise false, which + * indicates that the command shall be handled by other actions. + */ + abstract boolean processCommand(HdmiCecMessage cmd); + + /** + * Called when the action should handle the timer event it created before. + * + * <p>CEC standard mandates each command transmission should be responded within + * certain period of time. The method is called when the timer it created as it transmitted + * a command gets expired. Inner logic should take an appropriate action. + * + * @param state the state associated with the time when the timer was created + */ + abstract void handleTimerEvent(int state); + + /** + * Timer handler interface used for FeatureAction classes. + */ + interface ActionTimer { + /** + * Send a timer message. + * + * Also carries the state of the action when the timer is created. Later this state is + * compared to the one the action is in when it receives the timer to let the action tell + * the right timer to handle. + * + * @param state state of the action is in + * @param delayMillis amount of delay for the timer + */ + void sendTimerMessage(int state, long delayMillis); + } + + private class ActionTimerHandler extends Handler implements ActionTimer { + + public ActionTimerHandler(Looper looper) { + super(looper); + } + + @Override + public void sendTimerMessage(int state, long delayMillis) { + sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state), delayMillis); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_TIMEOUT: + handleTimerEvent(msg.arg1); + break; + default: + Slog.w(TAG, "Unsupported message:" + msg.what); + break; + } + } + } + + private ActionTimer createActionTimer(Looper looper) { + return new ActionTimerHandler(looper); + } + + // Add a new timer. The timer event will come to mActionTimer.handleMessage() in + // delayMillis. + protected void addTimer(int state, int delayMillis) { + mActionTimer.sendTimerMessage(state, delayMillis); + } + + static HdmiCecMessage buildCommand(int src, int dst, int opcode, byte[] params) { + return new HdmiCecMessage(src, dst, opcode, params); + } + + // Build a CEC command that does not have parameter. + static HdmiCecMessage buildCommand(int src, int dst, int opcode) { + return new HdmiCecMessage(src, dst, opcode, HdmiCecMessage.EMPTY_PARAM); + } + + protected final void sendCommand(HdmiCecMessage cmd) { + mService.sendCecCommand(cmd); + } + + protected final void sendBroadcastCommand(int opcode, byte[] param) { + sendCommand(buildCommand(mSourceAddress, HdmiCec.ADDR_BROADCAST, opcode, param)); + } + + /** + * Finish up the action. Reset the state, and remove itself from the action queue. + */ + protected void finish() { + mState = STATE_NONE; + removeAction(this); + } + + /** + * Remove the action from the action queue. This is called after the action finishes + * its role. + * + * @param action + */ + private void removeAction(FeatureAction action) { + mService.removeAction(action); + } + + // Utility methods for generating parameter byte arrays for CEC commands. + protected static byte[] uiCommandParam(int uiCommand) { + return new byte[] {(byte) uiCommand}; + } + + protected static byte[] physicalAddressParam(int physicalAddress) { + return new byte[] { + (byte) ((physicalAddress >> 8) & 0xFF), + (byte) (physicalAddress & 0xFF) + }; + } + + protected static byte[] pathPairParam(int oldPath, int newPath) { + return new byte[] { + (byte) ((oldPath >> 8) & 0xFF), (byte) (oldPath & 0xFF), + (byte) ((newPath >> 8) & 0xFF), (byte) (newPath & 0xFF) + }; + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 7c1995ed10cb..f99c71789204 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -18,6 +18,8 @@ package com.android.server.hdmi; import android.annotation.Nullable; import android.content.Context; +import android.hardware.hdmi.HdmiCecDeviceInfo; +import android.hardware.hdmi.HdmiCecMessage; import android.os.HandlerThread; import android.os.Looper; import android.util.Slog; @@ -77,4 +79,41 @@ public final class HdmiControlService extends SystemService { Looper getServiceLooper() { return Looper.myLooper(); } + + /** + * Add a new {@link FeatureAction} to the action queue. + * + * @param action {@link FeatureAction} to add + */ + void addAction(FeatureAction action) { + // TODO: Implement this. + } + + + /** + * Remove the given {@link FeatureAction} object from the action queue. + * + * @param action {@link FeatureAction} to add + */ + void removeAction(FeatureAction action) { + // TODO: Implement this. + } + + /** + * Transmit a CEC command to CEC bus. + * + * @param command CEC command to send out + */ + void sendCecCommand(HdmiCecMessage command) { + // TODO: Implement this. + } + + /** + * Add a new {@link HdmiCecDeviceInfo} to controller. + * + * @param deviceInfo new device information object to add + */ + void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { + // TODO: Implement this. + } } diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java new file mode 100644 index 000000000000..c84a0672ef6d --- /dev/null +++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java @@ -0,0 +1,165 @@ +/* + * 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.HdmiCecDeviceInfo; +import android.hardware.hdmi.HdmiCecMessage; +import android.util.Slog; + +import java.io.UnsupportedEncodingException; + +/** + * Feature action that discovers the information of a newly found logical device. + * + * This action is created when receiving <Report Physical Address>, a CEC command a newly + * connected HDMI-CEC device broadcasts to announce its advent. Additional commands are issued in + * this action to gather more information on the device such as OSD name and device vendor ID. + * + * <p>The result is made in the form of {@link HdmiCecDeviceInfo} object, and passed to service + * for the management through its life cycle. + * + * <p>Package-private, accessed by {@link HdmiControlService} only. + */ +final class NewDeviceAction extends FeatureAction { + + private static final String TAG = "NewDeviceAction"; + + // State in which the action sent <Give OSD Name> and is waiting for <Set OSD Name> + // that contains the name of the device for display on screen. + static final int STATE_WAITING_FOR_SET_OSD_NAME = 1; + + // State in which the action sent <Give Device Vendor ID> and is waiting for + // <Device Vendor ID> that contains the vendor ID of the device. + static final int STATE_WAITING_FOR_DEVICE_VENDOR_ID = 2; + + private final int mDeviceLogicalAddress; + private final int mDevicePhysicalAddress; + + private int mVendorId; + private String mDisplayName; + + /** + * Constructor. + * + * @param service {@link HdmiControlService} instance + * @param sourceAddress logical address to be used as source address + * @param deviceLogicalAddress logical address of the device in interest + * @param devicePhysicalAddress physical address of the device in interest + */ + NewDeviceAction(HdmiControlService service, int sourceAddress, int deviceLogicalAddress, + int devicePhysicalAddress) { + super(service, sourceAddress); + mDeviceLogicalAddress = deviceLogicalAddress; + mDevicePhysicalAddress = devicePhysicalAddress; + mVendorId = HdmiCec.UNKNOWN_VENDOR_ID; + } + + @Override + public boolean start() { + sendCommand( + buildCommand(mSourceAddress, mDeviceLogicalAddress, HdmiCec.MESSAGE_GET_OSD_NAME)); + mState = STATE_WAITING_FOR_SET_OSD_NAME; + addTimer(mState, TIMEOUT_MS); + return true; + } + + @Override + public boolean processCommand(HdmiCecMessage cmd) { + // For the logical device in interest, we want two more pieces of information - + // osd name and vendor id. They are requested in sequence. In case we don't + // get the expected responses (either by timeout or by receiving <feature abort> command), + // set them to a default osd name and unknown vendor id respectively. + int opcode = cmd.getOpcode(); + int src = cmd.getSource(); + byte[] params = cmd.getParams(); + + if (mDeviceLogicalAddress != src) { + return false; + } + + if (mState == STATE_WAITING_FOR_SET_OSD_NAME) { + if (opcode == HdmiCec.MESSAGE_SET_OSD_NAME) { + try { + mDisplayName = new String(params, "US-ASCII"); + } catch (UnsupportedEncodingException e) { + Slog.e(TAG, "Failed to get OSD name: " + e.getMessage()); + } + mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID; + requestVendorId(); + return true; + } else if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) { + int requestOpcode = params[1]; + if (requestOpcode == HdmiCec.MESSAGE_SET_OSD_NAME) { + mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID; + requestVendorId(); + return true; + } + } + } else if (mState == STATE_WAITING_FOR_DEVICE_VENDOR_ID) { + if (opcode == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) { + if (params.length == 3) { + mVendorId = (params[0] << 16) + (params[1] << 8) + params[2]; + } else { + Slog.e(TAG, "Failed to get device vendor ID: "); + } + addDeviceInfo(); + finish(); + return true; + } else if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) { + int requestOpcode = params[1]; + if (requestOpcode == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) { + addDeviceInfo(); + finish(); + return true; + } + } + } + return false; + } + + private void requestVendorId() { + sendCommand(buildCommand(mSourceAddress, mDeviceLogicalAddress, + HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID)); + addTimer(mState, TIMEOUT_MS); + } + + private void addDeviceInfo() { + if (mDisplayName == null) { + mDisplayName = HdmiCec.getDefaultDeviceName(mDeviceLogicalAddress); + } + mService.addDeviceInfo(new HdmiCecDeviceInfo( + mDeviceLogicalAddress, mDevicePhysicalAddress, + HdmiCec.getTypeFromAddress(mDeviceLogicalAddress), + mVendorId, mDisplayName)); + } + + @Override + public void handleTimerEvent(int state) { + if (mState == STATE_NONE || mState != state) { + return; + } + if (state == STATE_WAITING_FOR_SET_OSD_NAME) { + // Osd name request timed out. Try vendor id + mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID; + requestVendorId(); + } else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) { + // vendor id timed out. Go ahead creating the device info what we've got so far. + addDeviceInfo(); + finish(); + } + } +} |