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

Commit 67ea521d authored by Jungshik Jang's avatar Jungshik Jang
Browse files

Add several actions for ARC (Audio Return Channel)

ARC channel is established by both TV and AV Reciever.
From TV, it sends <Request ARC Initiation> and AVR
responds with <Initiate ARC>.
From AVR, it can be initiated by sending <Initiate ARC> directly
to TV.
Once TV receives <Initiate ARC> it sets up ARC internally
and replies <Report ARC Initiated> to AVR.
Termination steps are almost same except for message name
(use Terminate instread of Initiation).

In order to implement the above steps, this change introduces
following classes.

RequestArcInitiation(Termination)Action:
  handles <Request ARC Initiation> (<Request ARC Termination>)
  RequestArcAction handles common logic of them.

SetArcTransmissionStateAction:
  handles ARC set up, enabling/disabling ARC and
  reports results to AVR.

<Initiate ARC> and <Terminate ARC> handles directly in
HdmiControlService

Along with this, this change has implmentation for
add&removeAction. To avoid synchronization issue
they are isolated to main thread.

Change-Id: I3c5cf7c777e6c1de50d63ce4643b191dfe15fe1f
parent 193909da
Loading
Loading
Loading
Loading
+24 −3
Original line number Diff line number Diff line
@@ -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.
     *
     * <p>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);
    }

+5 −3
Original line number Diff line number Diff line
@@ -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);
+44 −0
Original line number Diff line number Diff line
@@ -185,6 +185,50 @@ public class HdmiCecMessageBuilder {
        return buildCommand(src, dest, HdmiCec.MESSAGE_CEC_VERSION, params);
    }

    /**
     * Build &lt;Request Arc Initiation&gt;
     *
     * @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 &lt;Request Arc Termination&gt;
     *
     * @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 &lt;Report Arc Initiated&gt;
     *
     * @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 &lt;Report Arc Terminated&gt;
     *
     * @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.
     *
+109 −11
Original line number Diff line number Diff line
@@ -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<FeatureAction> 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);
    }
@@ -83,35 +97,83 @@ public final class HdmiControlService extends SystemService {
     * <p>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 <T extends FeatureAction> void removeAction(final Class<T> clazz) {
        runOnServiceThread(new Runnable() {
            @Override
            public void run() {
                Iterator<FeatureAction> 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);
    }

    /**
@@ -142,6 +204,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
            // <Give Device Power Status> and <Request Active Source> handler.
            default:
@@ -150,6 +218,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 <Initiate Arc> is started by <Request ARC Initiation>
        // 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 <Terminate Arc> is started by <Request ARC Termination>
        // 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(),
+100 −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.util.Slog;

/**
 * Base feature action class for &lt;Request ARC Initiation&gt;/&lt;Request ARC Termination&gt;.
 */
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 <Feature Abort> here and, both <Initiate ARC> and <Terminate ARC>
            // are handled in HdmiControlService itself because both can be
            // received wihtout <Request ARC Initiation> or <Request ARC Termination>.
            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();
    }
}
Loading