DeviceSelectAction.java revision b509c2ecd99619248b7a07fb0fa978bb27f25cc3
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.hdmi.HdmiControlManager;
21import android.hardware.hdmi.HdmiTvClient;
22import android.hardware.hdmi.IHdmiControlCallback;
23import android.os.RemoteException;
24import android.util.Slog;
25
26import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
27import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
28
29/**
30 * Handles an action that selects a logical device as a new active source.
31 *
32 * Triggered by {@link HdmiTvClient}, attempts to select the given target device
33 * for a new active source. It does its best to wake up the target in standby mode
34 * before issuing the command >Set Stream path<.
35 */
36final class DeviceSelectAction extends HdmiCecFeatureAction {
37    private static final String TAG = "DeviceSelect";
38
39    // Time in milliseconds we wait for the device power status to switch to 'Standby'
40    private static final int TIMEOUT_TRANSIT_TO_STANDBY_MS = 5 * 1000;
41
42    // Time in milliseconds we wait for the device power status to turn to 'On'.
43    private static final int TIMEOUT_POWER_ON_MS = 5 * 1000;
44
45    // Time in milliseconds we wait for <Active Source>.
46    private static final int TIMEOUT_ACTIVE_SOURCE_MS = 20 * 1000;
47
48    // The number of times we try to wake up the target device before we give up
49    // and just send <Set Stream Path>.
50    private static final int LOOP_COUNTER_MAX = 20;
51
52    // State in which we wait for <Report Power Status> to come in response to the command
53    // <Give Device Power Status> we have sent.
54    private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1;
55
56    // State in which we wait for the device power status to switch to 'Standby'.
57    // We wait till the status becomes 'Standby' before we send <Set Stream Path>
58    // to wake up the device again.
59    private static final int STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY = 2;
60
61    // State in which we wait for the device power status to switch to 'on'. We wait
62    // maximum 100 seconds (20 * 5) before we give up and just send <Set Stream Path>.
63    private static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3;
64
65    // State in which we wait for the <Active Source> in response to the command
66    // <Set Stream Path> we have sent. We wait as much as TIMEOUT_ACTIVE_SOURCE_MS
67    // before we give up and mark the action as failure.
68    private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 4;
69
70    private final HdmiDeviceInfo mTarget;
71    private final IHdmiControlCallback mCallback;
72    private final HdmiCecMessage mGivePowerStatus;
73
74    private int mPowerStatusCounter = 0;
75
76    /**
77     * Constructor.
78     *
79     * @param source {@link HdmiCecLocalDevice} instance
80     * @param target target logical device that will be a new active source
81     * @param callback callback object
82     */
83    public DeviceSelectAction(HdmiCecLocalDeviceTv source,
84            HdmiDeviceInfo target, IHdmiControlCallback callback) {
85        super(source);
86        mCallback = callback;
87        mTarget = target;
88        mGivePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
89                getSourceAddress(), getTargetAddress());
90    }
91
92    int getTargetAddress() {
93        return mTarget.getLogicalAddress();
94    }
95
96    @Override
97    public boolean start() {
98        // Seq #9
99        queryDevicePowerStatus();
100        return true;
101    }
102
103    private void queryDevicePowerStatus() {
104        sendCommand(mGivePowerStatus, new SendMessageCallback() {
105            @Override
106            public void onSendCompleted(int error) {
107                if (error == Constants.SEND_RESULT_NAK) {
108                    invokeCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
109                    finish();
110                    return;
111                }
112            }
113        });
114        mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
115        addTimer(mState, HdmiConfig.TIMEOUT_MS);
116    }
117
118    @Override
119    public boolean processCommand(HdmiCecMessage cmd) {
120        if (cmd.getSource() != getTargetAddress()) {
121            return false;
122        }
123        int opcode = cmd.getOpcode();
124        byte[] params = cmd.getParams();
125
126        switch (mState) {
127            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
128                if (opcode == Constants.MESSAGE_REPORT_POWER_STATUS) {
129                    return handleReportPowerStatus(params[0]);
130                }
131                return false;
132            case STATE_WAIT_FOR_ACTIVE_SOURCE:
133                if (opcode == Constants.MESSAGE_ACTIVE_SOURCE) {
134                    int physicalAddress = HdmiUtils.twoBytesToInt(params);
135                    ActiveSourceHandler
136                            .create((HdmiCecLocalDeviceTv) localDevice(), mCallback)
137                            .process(ActiveSource.of(cmd.getSource(), physicalAddress));
138                    finish();
139                    return true;
140                }
141                return false;
142            default:
143                break;
144        }
145        return false;
146    }
147
148    private boolean handleReportPowerStatus(int powerStatus) {
149        switch (powerStatus) {
150            case HdmiControlManager.POWER_STATUS_ON:
151                sendSetStreamPath();
152                return true;
153            case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
154                if (mPowerStatusCounter < 4) {
155                    mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY;
156                    addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS);
157                } else {
158                    sendSetStreamPath();
159                }
160                return true;
161            case HdmiControlManager.POWER_STATUS_STANDBY:
162                if (mPowerStatusCounter == 0) {
163                    turnOnDevice();
164                } else {
165                    sendSetStreamPath();
166                }
167                return true;
168            case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
169                if (mPowerStatusCounter < LOOP_COUNTER_MAX) {
170                    mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
171                    addTimer(mState, TIMEOUT_POWER_ON_MS);
172                } else {
173                    sendSetStreamPath();
174                }
175                return true;
176        }
177        return false;
178    }
179
180    private void turnOnDevice() {
181        sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
182                HdmiCecKeycode.CEC_KEYCODE_POWER);
183        sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
184                HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION);
185        mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
186        addTimer(mState, TIMEOUT_POWER_ON_MS);
187    }
188
189    private void sendSetStreamPath() {
190        sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(
191                getSourceAddress(), mTarget.getPhysicalAddress()));
192        mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
193        addTimer(mState, TIMEOUT_ACTIVE_SOURCE_MS);
194    }
195
196    @Override
197    public void handleTimerEvent(int timeoutState) {
198        if (mState != timeoutState) {
199            Slog.w(TAG, "Timer in a wrong state. Ignored.");
200            return;
201        }
202        switch (mState) {
203            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
204                if (tv().isPowerStandbyOrTransient()) {
205                    invokeCallback(HdmiControlManager.RESULT_INCORRECT_MODE);
206                    finish();
207                    return;
208                }
209                sendSetStreamPath();
210                break;
211            case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY:
212            case STATE_WAIT_FOR_DEVICE_POWER_ON:
213                mPowerStatusCounter++;
214                queryDevicePowerStatus();
215                break;
216            case STATE_WAIT_FOR_ACTIVE_SOURCE:
217                invokeCallback(HdmiControlManager.RESULT_TIMEOUT);
218                finish();
219                break;
220        }
221    }
222
223    private void invokeCallback(int result) {
224        if (mCallback == null) {
225            return;
226        }
227        try {
228            mCallback.onComplete(result);
229        } catch (RemoteException e) {
230            Slog.e(TAG, "Callback failed:" + e);
231        }
232    }
233}
234