Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 043aa252 authored by Jinsuk Kim's avatar Jinsuk Kim Committed by Android (Google) Code Review
Browse files

Merge "Define Feature action for obtaining info on a new CEC device"

parents c74b3cb6 c70d2295
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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;
+4 −0
Original line number Diff line number Diff line
@@ -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;
+211 −0
Original line number Diff line number Diff line
/*
 * 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)
        };
    }
}
+39 −0
Original line number Diff line number Diff line
@@ -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.
    }
}
+165 −0
Original line number Diff line number Diff line
/*
 * 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 &lt;Report Physical Address&gt;, 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();
        }
    }
}