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