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