DeviceSelectAction.java revision 8333571bd5e0a08773a1679964f8d96227af3356
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        sendRemoteKeyCommand(HdmiConstants.UI_COMMAND_POWER);
168        sendRemoteKeyCommand(HdmiConstants.UI_COMMAND_POWER_ON_FUNCTION);
169        mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
170        addTimer(mState, TIMEOUT_POWER_ON_MS);
171    }
172
173    private void sendSetStreamPath() {
174        sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(
175                getSourceAddress(), mTarget.getPhysicalAddress()));
176        mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
177        addTimer(mState, TIMEOUT_ACTIVE_SOURCE_MS);
178    }
179
180    private void sendRemoteKeyCommand(int keyCode) {
181        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(),
182                mTarget.getLogicalAddress(), keyCode));
183        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
184                mTarget.getLogicalAddress()));
185    }
186
187    @Override
188    public void handleTimerEvent(int timeoutState) {
189        if (mState != timeoutState) {
190            Slog.w(TAG, "Timer in a wrong state. Ignored.");
191            return;
192        }
193        switch (mState) {
194            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
195                sendSetStreamPath();
196                break;
197            case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY:
198            case STATE_WAIT_FOR_DEVICE_POWER_ON:
199                mPowerStatusCounter++;
200                queryDevicePowerStatus();
201                break;
202            case STATE_WAIT_FOR_ACTIVE_SOURCE:
203                // TODO: Remove the banner
204                //       Display banner "Communication failed. Please check your cable or connection"
205                invokeCallback(HdmiCec.RESULT_TIMEOUT);
206                finish();
207                break;
208        }
209    }
210
211    private void invokeCallback(int result) {
212        if (mCallback == null) {
213            return;
214        }
215        try {
216            mCallback.onComplete(result);
217        } catch (RemoteException e) {
218            Slog.e(TAG, "Callback failed:" + e);
219        }
220    }
221
222    int getTargetAddress() {
223        return mTarget.getLogicalAddress();
224    }
225}
226