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 
17 package com.android.server.hdmi;
18 
19 import android.hardware.hdmi.HdmiControlManager;
20 import android.hardware.hdmi.HdmiDeviceInfo;
21 import android.hardware.hdmi.HdmiTvClient;
22 import android.hardware.hdmi.IHdmiControlCallback;
23 import android.hardware.tv.cec.V1_0.SendMessageResult;
24 import android.os.RemoteException;
25 import android.util.Slog;
26 import 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  */
35 final class DeviceSelectAction extends HdmiCecFeatureAction {
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     // The number of times we try to wake up the target device before we give up
45     // and just send <Set Stream Path>.
46     private static final int LOOP_COUNTER_MAX = 20;
47 
48     // State in which we wait for <Report Power Status> to come in response to the command
49     // <Give Device Power Status> we have sent.
50     private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1;
51 
52     // State in which we wait for the device power status to switch to 'Standby'.
53     // We wait till the status becomes 'Standby' before we send <Set Stream Path>
54     // to wake up the device again.
55     private static final int STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY = 2;
56 
57     // State in which we wait for the device power status to switch to 'on'. We wait
58     // maximum 100 seconds (20 * 5) before we give up and just send <Set Stream Path>.
59     private static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3;
60 
61     private final HdmiDeviceInfo mTarget;
62     private final IHdmiControlCallback mCallback;
63     private final HdmiCecMessage mGivePowerStatus;
64 
65     private int mPowerStatusCounter = 0;
66 
67     /**
68      * Constructor.
69      *
70      * @param source {@link HdmiCecLocalDevice} instance
71      * @param target target logical device that will be a new active source
72      * @param callback callback object
73      */
DeviceSelectAction(HdmiCecLocalDeviceTv source, HdmiDeviceInfo target, IHdmiControlCallback callback)74     public DeviceSelectAction(HdmiCecLocalDeviceTv source,
75             HdmiDeviceInfo target, IHdmiControlCallback callback) {
76         super(source);
77         mCallback = callback;
78         mTarget = target;
79         mGivePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
80                 getSourceAddress(), getTargetAddress());
81     }
82 
getTargetAddress()83     int getTargetAddress() {
84         return mTarget.getLogicalAddress();
85     }
86 
87     @Override
start()88     public boolean start() {
89         // Seq #9
90         queryDevicePowerStatus();
91         return true;
92     }
93 
queryDevicePowerStatus()94     private void queryDevicePowerStatus() {
95         sendCommand(mGivePowerStatus, new SendMessageCallback() {
96             @Override
97             public void onSendCompleted(int error) {
98                 if (error != SendMessageResult.SUCCESS) {
99                     invokeCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
100                     finish();
101                     return;
102                 }
103             }
104         });
105         mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
106         addTimer(mState, HdmiConfig.TIMEOUT_MS);
107     }
108 
109     @Override
processCommand(HdmiCecMessage cmd)110     public boolean processCommand(HdmiCecMessage cmd) {
111         if (cmd.getSource() != getTargetAddress()) {
112             return false;
113         }
114         int opcode = cmd.getOpcode();
115         byte[] params = cmd.getParams();
116 
117         switch (mState) {
118             case STATE_WAIT_FOR_REPORT_POWER_STATUS:
119                 if (opcode == Constants.MESSAGE_REPORT_POWER_STATUS) {
120                     return handleReportPowerStatus(params[0]);
121                 }
122                 return false;
123             default:
124                 break;
125         }
126         return false;
127     }
128 
handleReportPowerStatus(int powerStatus)129     private boolean handleReportPowerStatus(int powerStatus) {
130         switch (powerStatus) {
131             case HdmiControlManager.POWER_STATUS_ON:
132                 sendSetStreamPath();
133                 return true;
134             case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
135                 if (mPowerStatusCounter < 4) {
136                     mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY;
137                     addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS);
138                 } else {
139                     sendSetStreamPath();
140                 }
141                 return true;
142             case HdmiControlManager.POWER_STATUS_STANDBY:
143                 if (mPowerStatusCounter == 0) {
144                     turnOnDevice();
145                 } else {
146                     sendSetStreamPath();
147                 }
148                 return true;
149             case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
150                 if (mPowerStatusCounter < LOOP_COUNTER_MAX) {
151                     mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
152                     addTimer(mState, TIMEOUT_POWER_ON_MS);
153                 } else {
154                     sendSetStreamPath();
155                 }
156                 return true;
157         }
158         return false;
159     }
160 
turnOnDevice()161     private void turnOnDevice() {
162         sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
163                 HdmiCecKeycode.CEC_KEYCODE_POWER);
164         sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
165                 HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION);
166         mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
167         addTimer(mState, TIMEOUT_POWER_ON_MS);
168     }
169 
sendSetStreamPath()170     private void sendSetStreamPath() {
171         // Turn the active source invalidated, which remains so till <Active Source> comes from
172         // the selected device.
173         tv().getActiveSource().invalidate();
174         tv().setActivePath(mTarget.getPhysicalAddress());
175         sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(
176                 getSourceAddress(), mTarget.getPhysicalAddress()));
177         invokeCallback(HdmiControlManager.RESULT_SUCCESS);
178         finish();
179     }
180 
181     @Override
handleTimerEvent(int timeoutState)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                 if (tv().isPowerStandbyOrTransient()) {
190                     invokeCallback(HdmiControlManager.RESULT_INCORRECT_MODE);
191                     finish();
192                     return;
193                 }
194                 sendSetStreamPath();
195                 break;
196             case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY:
197             case STATE_WAIT_FOR_DEVICE_POWER_ON:
198                 mPowerStatusCounter++;
199                 queryDevicePowerStatus();
200                 break;
201         }
202     }
203 
invokeCallback(int result)204     private void invokeCallback(int result) {
205         if (mCallback == null) {
206             return;
207         }
208         try {
209             mCallback.onComplete(result);
210         } catch (RemoteException e) {
211             Slog.e(TAG, "Callback failed:" + e);
212         }
213     }
214 }
215