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