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