SystemAudioAction.java revision 79c58a4b97f27ede6a1b680d2fece9c2a0edf7b7
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.HdmiCec;
20import android.hardware.hdmi.HdmiCecMessage;
21
22/**
23 * Base feature action class for SystemAudioActionFromTv and SystemAudioActionFromAvr.
24 */
25abstract class SystemAudioAction extends FeatureAction {
26    private static final String TAG = "SystemAudioAction";
27
28    // State in which waits for <SetSystemAudioMode>.
29    private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 1;
30
31    // State in which waits for <ReportAudioStatus>.
32    private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 2;
33
34    private static final int MAX_SEND_RETRY_COUNT = 2;
35
36    private static final int ON_TIMEOUT_MS = 5000;
37    private static final int OFF_TIMEOUT_MS = TIMEOUT_MS;
38
39    // Logical address of AV Receiver.
40    protected final int mAvrLogicalAddress;
41
42    // The target audio status of the action, whether to enable the system audio mode or not.
43    protected boolean mTargetAudioStatus;
44
45    private int mSendRetryCount = 0;
46
47    /**
48     * Constructor
49     *
50     * @param source {@link HdmiCecLocalDevice} instance
51     * @param avrAddress logical address of AVR device
52     * @param targetStatus Whether to enable the system audio mode or not
53     * @throw IllegalArugmentException if device type of sourceAddress and avrAddress is invalid
54     */
55    SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus) {
56        super(source);
57        HdmiUtils.verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
58        mAvrLogicalAddress = avrAddress;
59        mTargetAudioStatus = targetStatus;
60    }
61
62    protected void sendSystemAudioModeRequest() {
63        int avrPhysicalAddress = tv().getAvrDeviceInfo().getPhysicalAddress();
64        HdmiCecMessage command = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
65                getSourceAddress(),
66                mAvrLogicalAddress, avrPhysicalAddress, mTargetAudioStatus);
67        sendCommand(command, new HdmiControlService.SendMessageCallback() {
68            @Override
69            public void onSendCompleted(int error) {
70                if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
71                    mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE;
72                    addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS);
73                } else {
74                    setSystemAudioMode(false);
75                    finish();
76                }
77            }
78        });
79    }
80
81    private void handleSendSystemAudioModeRequestTimeout() {
82        if (!mTargetAudioStatus  // Don't retry for Off case.
83                || mSendRetryCount++ >= MAX_SEND_RETRY_COUNT) {
84            setSystemAudioMode(false);
85            finish();
86            return;
87        }
88        sendSystemAudioModeRequest();
89    }
90
91    protected void setSystemAudioMode(boolean mode) {
92        tv().setSystemAudioMode(mode);
93    }
94
95    protected void sendGiveAudioStatus() {
96        HdmiCecMessage command = HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(),
97                mAvrLogicalAddress);
98        sendCommand(command, new HdmiControlService.SendMessageCallback() {
99            @Override
100            public void onSendCompleted(int error) {
101                if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
102                    mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
103                    addTimer(mState, TIMEOUT_MS);
104                } else {
105                    handleSendGiveAudioStatusFailure();
106                }
107            }
108        });
109    }
110
111    private void handleSendGiveAudioStatusFailure() {
112        // TODO: Notify the failure status.
113
114        int uiCommand = tv().getSystemAudioMode()
115                ? HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION  // SystemAudioMode: ON
116                : HdmiConstants.UI_COMMAND_MUTE_FUNCTION;           // SystemAudioMode: OFF
117        sendUserControlPressedAndReleased(uiCommand);
118        finish();
119    }
120
121    private void sendUserControlPressedAndReleased(int uiCommand) {
122        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
123                getSourceAddress(), mAvrLogicalAddress, uiCommand));
124        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
125                getSourceAddress(), mAvrLogicalAddress));
126    }
127
128    @Override
129    final boolean processCommand(HdmiCecMessage cmd) {
130        switch (mState) {
131            case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE:
132                // TODO: Handle <FeatureAbort> of <SystemAudioModeRequest>
133                if (cmd.getOpcode() != HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE
134                        || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) {
135                    return false;
136                }
137                boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
138                if (receivedStatus == mTargetAudioStatus) {
139                    setSystemAudioMode(receivedStatus);
140                    sendGiveAudioStatus();
141                } else {
142                    // Unexpected response, consider the request is newly initiated by AVR.
143                    // To return 'false' will initiate new SystemAudioActionFromAvr by the control
144                    // service.
145                    finish();
146                    return false;
147                }
148                return true;
149
150            case STATE_WAIT_FOR_REPORT_AUDIO_STATUS:
151                // TODO: Handle <FeatureAbort> of <GiveAudioStatus>
152                if (cmd.getOpcode() != HdmiCec.MESSAGE_REPORT_AUDIO_STATUS
153                        || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) {
154                    return false;
155                }
156                byte[] params = cmd.getParams();
157                if (params.length > 0) {
158                    boolean mute = (params[0] & 0x80) == 0x80;
159                    int volume = params[0] & 0x7F;
160                    tv().setAudioStatus(mute, volume);
161                    if (mTargetAudioStatus && mute || !mTargetAudioStatus && !mute) {
162                        // Toggle AVR's mute status to match with the system audio status.
163                        sendUserControlPressedAndReleased(HdmiConstants.UI_COMMAND_MUTE);
164                    }
165                }
166                finish();
167                return true;
168        }
169        return false;
170    }
171
172    protected void removeSystemAudioActionInProgress() {
173        removeActionExcept(SystemAudioActionFromTv.class, this);
174        removeActionExcept(SystemAudioActionFromAvr.class, this);
175    }
176
177    @Override
178    final void handleTimerEvent(int state) {
179        if (mState != state) {
180            return;
181        }
182        switch (mState) {
183            case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE:
184                handleSendSystemAudioModeRequestTimeout();
185                return;
186            case STATE_WAIT_FOR_REPORT_AUDIO_STATUS:
187                handleSendGiveAudioStatusFailure();
188                return;
189        }
190    }
191}
192