blob: c684a56ec5d48fcd645789aa70ce246d0aa71df4 [file] [log] [blame]
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001/*
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
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090019import android.hardware.hdmi.HdmiControlManager;
Donghyun Chobc6e3722016-11-04 05:25:52 +090020import android.hardware.hdmi.HdmiDeviceInfo;
Jungshik Jang79c58a42014-06-16 16:45:36 +090021import android.hardware.hdmi.HdmiTvClient;
22import android.hardware.hdmi.IHdmiControlCallback;
Donghyun Chobc6e3722016-11-04 05:25:52 +090023import android.hardware.tv.cec.V1_0.SendMessageResult;
Jinsuk Kima6ce7702014-05-11 06:54:49 +090024import android.os.RemoteException;
25import android.util.Slog;
Jinsuk Kimb38cd682014-07-07 08:05:03 +090026import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
27
Jinsuk Kima6ce7702014-05-11 06:54:49 +090028/**
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 */
Jungshik Jangb509c2e2014-08-07 13:45:01 +090035final class DeviceSelectAction extends HdmiCecFeatureAction {
Jinsuk Kima6ce7702014-05-11 06:54:49 +090036 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
Jinsuk Kima6ce7702014-05-11 06:54:49 +090044 // 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
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090061 private final HdmiDeviceInfo mTarget;
Jinsuk Kima6ce7702014-05-11 06:54:49 +090062 private final IHdmiControlCallback mCallback;
Jinsuk Kimb38cd682014-07-07 08:05:03 +090063 private final HdmiCecMessage mGivePowerStatus;
Jinsuk Kima6ce7702014-05-11 06:54:49 +090064
65 private int mPowerStatusCounter = 0;
66
67 /**
68 * Constructor.
69 *
Jungshik Jang79c58a42014-06-16 16:45:36 +090070 * @param source {@link HdmiCecLocalDevice} instance
Jinsuk Kima6ce7702014-05-11 06:54:49 +090071 * @param target target logical device that will be a new active source
72 * @param callback callback object
73 */
Jinsuk Kim83335712014-06-24 07:57:00 +090074 public DeviceSelectAction(HdmiCecLocalDeviceTv source,
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090075 HdmiDeviceInfo target, IHdmiControlCallback callback) {
Jungshik Jang79c58a42014-06-16 16:45:36 +090076 super(source);
Jinsuk Kima6ce7702014-05-11 06:54:49 +090077 mCallback = callback;
Jinsuk Kima6ce7702014-05-11 06:54:49 +090078 mTarget = target;
Jinsuk Kimb38cd682014-07-07 08:05:03 +090079 mGivePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
80 getSourceAddress(), getTargetAddress());
81 }
82
83 int getTargetAddress() {
84 return mTarget.getLogicalAddress();
Jinsuk Kima6ce7702014-05-11 06:54:49 +090085 }
86
87 @Override
88 public boolean start() {
Jinsuk Kimb38cd682014-07-07 08:05:03 +090089 // Seq #9
Jinsuk Kima6ce7702014-05-11 06:54:49 +090090 queryDevicePowerStatus();
91 return true;
92 }
93
94 private void queryDevicePowerStatus() {
Jinsuk Kimb38cd682014-07-07 08:05:03 +090095 sendCommand(mGivePowerStatus, new SendMessageCallback() {
96 @Override
97 public void onSendCompleted(int error) {
Donghyun Chobc6e3722016-11-04 05:25:52 +090098 if (error != SendMessageResult.SUCCESS) {
Jinsuk Kimb38cd682014-07-07 08:05:03 +090099 invokeCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
100 finish();
101 return;
102 }
103 }
104 });
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900105 mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
Jinsuk Kim5fba96d2014-07-11 11:51:34 +0900106 addTimer(mState, HdmiConfig.TIMEOUT_MS);
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900107 }
108
109 @Override
110 public boolean processCommand(HdmiCecMessage cmd) {
Jinsuk Kimb38cd682014-07-07 08:05:03 +0900111 if (cmd.getSource() != getTargetAddress()) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900112 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:
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900119 if (opcode == Constants.MESSAGE_REPORT_POWER_STATUS) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900120 return handleReportPowerStatus(params[0]);
121 }
122 return false;
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900123 default:
124 break;
125 }
126 return false;
127 }
128
129 private boolean handleReportPowerStatus(int powerStatus) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900130 switch (powerStatus) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900131 case HdmiControlManager.POWER_STATUS_ON:
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900132 sendSetStreamPath();
133 return true;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900134 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900135 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;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900142 case HdmiControlManager.POWER_STATUS_STANDBY:
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900143 if (mPowerStatusCounter == 0) {
144 turnOnDevice();
145 } else {
146 sendSetStreamPath();
147 }
148 return true;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900149 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900150 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
161 private void turnOnDevice() {
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900162 sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
Jungshik Jang210d73d2014-07-04 11:11:29 +0900163 HdmiCecKeycode.CEC_KEYCODE_POWER);
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900164 sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
Jungshik Jang210d73d2014-07-04 11:11:29 +0900165 HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION);
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900166 mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
167 addTimer(mState, TIMEOUT_POWER_ON_MS);
168 }
169
170 private void sendSetStreamPath() {
Jinsuk Kim75434972014-09-25 13:52:50 +0900171 // 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());
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900175 sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(
Jungshik Jang79c58a42014-06-16 16:45:36 +0900176 getSourceAddress(), mTarget.getPhysicalAddress()));
Jinsuk Kim8e083ec2014-08-12 18:10:21 +0900177 invokeCallback(HdmiControlManager.RESULT_SUCCESS);
178 finish();
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900179 }
180
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900181 @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:
Jinsuk Kimb38cd682014-07-07 08:05:03 +0900189 if (tv().isPowerStandbyOrTransient()) {
190 invokeCallback(HdmiControlManager.RESULT_INCORRECT_MODE);
191 finish();
192 return;
193 }
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900194 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;
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900201 }
202 }
203
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 }
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900214}