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