DeviceSelectAction.java revision c0c20d0522d7756d80f011e7a54bf3b51c78df41
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
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
70    private int mPowerStatusCounter = 0;
71
72    /**
73     * Constructor.
74     *
75     * @param source {@link HdmiCecLocalDevice} instance
76     * @param target target logical device that will be a new active source
77     * @param callback callback object
78     */
79    public DeviceSelectAction(HdmiCecLocalDeviceTv source,
80            HdmiCecDeviceInfo target, IHdmiControlCallback callback) {
81        super(source);
82        mCallback = callback;
83        mTarget = target;
84    }
85
86    @Override
87    public boolean start() {
88        // TODO: Call the logic that display a banner saying the select action got started.
89        queryDevicePowerStatus();
90        return true;
91    }
92
93    private void queryDevicePowerStatus() {
94        sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
95                getSourceAddress(), mTarget.getLogicalAddress()));
96        mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
97        addTimer(mState, TIMEOUT_MS);
98    }
99
100    @Override
101    public boolean processCommand(HdmiCecMessage cmd) {
102        if (cmd.getSource() != mTarget.getLogicalAddress()) {
103            return false;
104        }
105        int opcode = cmd.getOpcode();
106        byte[] params = cmd.getParams();
107
108        switch (mState) {
109            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
110                if (opcode == Constants.MESSAGE_REPORT_POWER_STATUS && params.length == 1) {
111                    return handleReportPowerStatus(params[0]);
112                }
113                return false;
114            case STATE_WAIT_FOR_ACTIVE_SOURCE:
115                if (opcode == Constants.MESSAGE_ACTIVE_SOURCE && params.length == 2) {
116                    int activePath = HdmiUtils.twoBytesToInt(params);
117                    ActiveSourceHandler
118                            .create((HdmiCecLocalDeviceTv) localDevice(), mCallback)
119                            .process(cmd.getSource(), activePath);
120                    finish();
121                    return true;
122                }
123                return false;
124            default:
125                break;
126        }
127        return false;
128    }
129
130    private boolean handleReportPowerStatus(int powerStatus) {
131        // TODO: Check TV's own status which might have been updated during the action.
132        //       If in 'Standby' or 'Transit to standby', remove the banner
133        //       and stop this action. Otherwise, send <Set Stream Path>
134        switch (powerStatus) {
135            case HdmiControlManager.POWER_STATUS_ON:
136                sendSetStreamPath();
137                return true;
138            case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
139                if (mPowerStatusCounter < 4) {
140                    mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY;
141                    addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS);
142                } else {
143                    sendSetStreamPath();
144                }
145                return true;
146            case HdmiControlManager.POWER_STATUS_STANDBY:
147                if (mPowerStatusCounter == 0) {
148                    turnOnDevice();
149                } else {
150                    sendSetStreamPath();
151                }
152                return true;
153            case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
154                if (mPowerStatusCounter < LOOP_COUNTER_MAX) {
155                    mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
156                    addTimer(mState, TIMEOUT_POWER_ON_MS);
157                } else {
158                    sendSetStreamPath();
159                }
160                return true;
161        }
162        return false;
163    }
164
165    private void turnOnDevice() {
166        sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
167                HdmiCecKeycode.CEC_KEYCODE_POWER);
168        sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
169                HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION);
170        mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
171        addTimer(mState, TIMEOUT_POWER_ON_MS);
172    }
173
174    private void sendSetStreamPath() {
175        sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(
176                getSourceAddress(), mTarget.getPhysicalAddress()));
177        mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
178        addTimer(mState, TIMEOUT_ACTIVE_SOURCE_MS);
179    }
180
181    @Override
182    public void handleTimerEvent(int timeoutState) {
183        if (mState != timeoutState) {
184            Slog.w(TAG, "Timer in a wrong state. Ignored.");
185            return;
186        }
187        switch (mState) {
188            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
189                sendSetStreamPath();
190                break;
191            case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY:
192            case STATE_WAIT_FOR_DEVICE_POWER_ON:
193                mPowerStatusCounter++;
194                queryDevicePowerStatus();
195                break;
196            case STATE_WAIT_FOR_ACTIVE_SOURCE:
197                // TODO: Remove the banner
198                //       Display banner "Communication failed. Please check your cable or connection"
199                invokeCallback(HdmiControlManager.RESULT_TIMEOUT);
200                finish();
201                break;
202        }
203    }
204
205    private void invokeCallback(int result) {
206        if (mCallback == null) {
207            return;
208        }
209        try {
210            mCallback.onComplete(result);
211        } catch (RemoteException e) {
212            Slog.e(TAG, "Callback failed:" + e);
213        }
214    }
215
216    int getTargetAddress() {
217        return mTarget.getLogicalAddress();
218    }
219}
220