1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.hdmi;
18
19import android.hardware.hdmi.HdmiDeviceInfo;
20import android.hardware.tv.cec.V1_0.SendMessageResult;
21import android.util.Slog;
22
23/**
24 * Feature action that handles enabling/disabling of ARC transmission channel.
25 * Once TV gets <Initiate ARC>, TV sends <Report ARC Initiated> to AV Receiver.
26 * If it fails or it gets <Terminate ARC>, TV just disables ARC.
27 */
28final class SetArcTransmissionStateAction extends HdmiCecFeatureAction {
29    private static final String TAG = "SetArcTransmissionStateAction";
30
31    // State in which the action sent <Rerpot Arc Initiated> and
32    // is waiting for time out. If it receives <Feature Abort> within timeout
33    // ARC should be disabled.
34    private static final int STATE_WAITING_TIMEOUT = 1;
35
36    private final boolean mEnabled;
37    private final int mAvrAddress;
38
39    /**
40     * @Constructor
41     *
42     * @param source {@link HdmiCecLocalDevice} instance
43     * @param enabled whether to enable ARC Transmission channel
44     */
45    SetArcTransmissionStateAction(HdmiCecLocalDevice source, int avrAddress,
46            boolean enabled) {
47        super(source);
48        HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV);
49        HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
50        mAvrAddress = avrAddress;
51        mEnabled = enabled;
52    }
53
54    @Override
55    boolean start() {
56        // Seq #37.
57        if (mEnabled) {
58            // Enable ARC status immediately before sending <Report Arc Initiated>.
59            // If AVR responds with <Feature Abort>, disable ARC status again.
60            // This is different from spec that says that turns ARC status to
61            // "Enabled" if <Report ARC Initiated> is acknowledged and no
62            // <Feature Abort> is received.
63            // But implemented this way to save the time having to wait for
64            // <Feature Abort>.
65            setArcStatus(true);
66            // If succeeds to send <Report ARC Initiated>, wait general timeout
67            // to check whether there is no <Feature Abort> for <Report ARC Initiated>.
68            mState = STATE_WAITING_TIMEOUT;
69            addTimer(mState, HdmiConfig.TIMEOUT_MS);
70            sendReportArcInitiated();
71        } else {
72            setArcStatus(false);
73            finish();
74        }
75        return true;
76    }
77
78    private void sendReportArcInitiated() {
79        HdmiCecMessage command =
80                HdmiCecMessageBuilder.buildReportArcInitiated(getSourceAddress(), mAvrAddress);
81        sendCommand(command, new HdmiControlService.SendMessageCallback() {
82            @Override
83            public void onSendCompleted(int error) {
84                switch (error) {
85                    case SendMessageResult.SUCCESS:
86                    case SendMessageResult.BUSY:
87                    case SendMessageResult.FAIL:
88                        // The result of the command transmission, unless it is an obvious
89                        // failure indicated by the target device (or lack thereof), should
90                        // not affect the ARC status. Ignores it silently.
91                        break;
92                    case SendMessageResult.NACK:
93                        // If <Report ARC Initiated> is negatively ack'ed, disable ARC and
94                        // send <Report ARC Terminated> directly.
95                        setArcStatus(false);
96                        HdmiLogger.debug("Failed to send <Report Arc Initiated>.");
97                        finish();
98                        break;
99                }
100            }
101        });
102    }
103
104    private void setArcStatus(boolean enabled) {
105        boolean wasEnabled = tv().setArcStatus(enabled);
106        Slog.i(TAG, "Change arc status [old:" + wasEnabled + ", new:" + enabled + "]");
107
108        // If enabled before and set to "disabled" and send <Report Arc Terminated> to
109        // av reciever.
110        if (!enabled && wasEnabled) {
111            sendCommand(HdmiCecMessageBuilder.buildReportArcTerminated(getSourceAddress(),
112                    mAvrAddress));
113        }
114    }
115
116    @Override
117    boolean processCommand(HdmiCecMessage cmd) {
118        if (mState != STATE_WAITING_TIMEOUT) {
119            return false;
120        }
121
122        int opcode = cmd.getOpcode();
123        if (opcode == Constants.MESSAGE_FEATURE_ABORT) {
124            int originalOpcode = cmd.getParams()[0] & 0xFF;
125            if (originalOpcode == Constants.MESSAGE_REPORT_ARC_INITIATED) {
126                HdmiLogger.debug("Feature aborted for <Report Arc Initiated>");
127                setArcStatus(false);
128                finish();
129                return true;
130            }
131        }
132        return false;
133    }
134
135    @Override
136    void handleTimerEvent(int state) {
137        if (mState != state || mState != STATE_WAITING_TIMEOUT) {
138            return;
139        }
140        // Expire timeout for <Feature Abort>.
141        finish();
142    }
143}
144