blob: 97a6e850654b5e8cda4aaf8eab6dce74846e371e [file] [log] [blame]
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +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
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090019import android.hardware.hdmi.HdmiDeviceInfo;
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090020import android.util.Slog;
21
22import com.android.internal.util.Preconditions;
23import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
24
25import java.io.UnsupportedEncodingException;
26import java.util.ArrayList;
27import java.util.List;
28
29/**
30 * Feature action that handles device discovery sequences.
31 * Device discovery is launched when TV device is woken from "Standby" state
32 * or enabled "Control for Hdmi" from disabled state.
33 *
34 * <p>Device discovery goes through the following steps.
35 * <ol>
36 * <li>Poll all non-local devices by sending &lt;Polling Message&gt;
37 * <li>Gather "Physical address" and "device type" of all acknowledged devices
38 * <li>Gather "OSD (display) name" of all acknowledge devices
39 * <li>Gather "Vendor id" of all acknowledge devices
40 * </ol>
Yuncheol Heo46350382014-12-03 22:29:45 +090041 * We attempt to get OSD name/vendor ID up to 5 times in case the communication fails.
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090042 */
Jungshik Jangb509c2e2014-08-07 13:45:01 +090043final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090044 private static final String TAG = "DeviceDiscoveryAction";
45
46 // State in which the action is waiting for device polling.
47 private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1;
48 // State in which the action is waiting for gathering physical address of non-local devices.
49 private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2;
50 // State in which the action is waiting for gathering osd name of non-local devices.
51 private static final int STATE_WAITING_FOR_OSD_NAME = 3;
52 // State in which the action is waiting for gathering vendor id of non-local devices.
53 private static final int STATE_WAITING_FOR_VENDOR_ID = 4;
54
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090055 /**
56 * Interface used to report result of device discovery.
57 */
58 interface DeviceDiscoveryCallback {
59 /**
60 * Called when device discovery is done.
61 *
62 * @param deviceInfos a list of all non-local devices. It can be empty list.
63 */
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090064 void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090065 }
66
67 // An internal container used to keep track of device information during
68 // this action.
69 private static final class DeviceInfo {
70 private final int mLogicalAddress;
71
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090072 private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
Jinsuk Kim2b152012014-07-25 08:22:26 +090073 private int mPortId = Constants.INVALID_PORT_ID;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090074 private int mVendorId = Constants.UNKNOWN_VENDOR_ID;
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090075 private String mDisplayName = "";
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090076 private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE;
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090077
78 private DeviceInfo(int logicalAddress) {
79 mLogicalAddress = logicalAddress;
80 }
81
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090082 private HdmiDeviceInfo toHdmiDeviceInfo() {
83 return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType,
Jinsuk Kim2b152012014-07-25 08:22:26 +090084 mVendorId, mDisplayName);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090085 }
86 }
87
88 private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
89 private final DeviceDiscoveryCallback mCallback;
90 private int mProcessedDeviceCount = 0;
Yuncheol Heo46350382014-12-03 22:29:45 +090091 private int mTimeoutRetry = 0;
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090092
93 /**
Jungshik Jange81e1082014-06-05 15:37:59 +090094 * Constructor.
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090095 *
Jungshik Jang79c58a42014-06-16 16:45:36 +090096 * @param source an instance of {@link HdmiCecLocalDevice}.
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +090097 */
Jungshik Jang79c58a42014-06-16 16:45:36 +090098 DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
99 super(source);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900100 mCallback = Preconditions.checkNotNull(callback);
101 }
102
103 @Override
104 boolean start() {
105 mDevices.clear();
106 mState = STATE_WAITING_FOR_DEVICE_POLLING;
107
Jungshik Jang79c58a42014-06-16 16:45:36 +0900108 pollDevices(new DevicePollingCallback() {
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900109 @Override
110 public void onPollingFinished(List<Integer> ackedAddress) {
111 if (ackedAddress.isEmpty()) {
Jungshik Jang8b308d92014-05-29 21:52:28 +0900112 Slog.v(TAG, "No device is detected.");
Jungshik Jangad2eefe2014-06-30 10:25:20 +0900113 wrapUpAndFinish();
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900114 return;
115 }
116
Jungshik Jang8b308d92014-05-29 21:52:28 +0900117 Slog.v(TAG, "Device detected: " + ackedAddress);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900118 allocateDevices(ackedAddress);
119 startPhysicalAddressStage();
120 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900121 }, Constants.POLL_ITERATION_REVERSE_ORDER
Jinsuk Kim5fba96d2014-07-11 11:51:34 +0900122 | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900123 return true;
124 }
125
126 private void allocateDevices(List<Integer> addresses) {
127 for (Integer i : addresses) {
128 DeviceInfo info = new DeviceInfo(i);
129 mDevices.add(info);
130 }
131 }
132
133 private void startPhysicalAddressStage() {
Jungshik Jang8b308d92014-05-29 21:52:28 +0900134 Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900135 mProcessedDeviceCount = 0;
136 mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
137
138 checkAndProceedStage();
139 }
140
141 private boolean verifyValidLogicalAddress(int address) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900142 return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED;
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900143 }
144
145 private void queryPhysicalAddress(int address) {
146 if (!verifyValidLogicalAddress(address)) {
147 checkAndProceedStage();
148 return;
149 }
150
151 mActionTimer.clearTimerMessage();
Jungshik Jange81e1082014-06-05 15:37:59 +0900152
153 // Check cache first and send request if not exist.
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900154 if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
Jungshik Jange81e1082014-06-05 15:37:59 +0900155 return;
156 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900157 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address));
Jinsuk Kim5fba96d2014-07-11 11:51:34 +0900158 addTimer(mState, HdmiConfig.TIMEOUT_MS);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900159 }
160
161 private void startOsdNameStage() {
Jungshik Jang8b308d92014-05-29 21:52:28 +0900162 Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900163 mProcessedDeviceCount = 0;
164 mState = STATE_WAITING_FOR_OSD_NAME;
165
166 checkAndProceedStage();
167 }
168
169 private void queryOsdName(int address) {
170 if (!verifyValidLogicalAddress(address)) {
171 checkAndProceedStage();
172 return;
173 }
174
175 mActionTimer.clearTimerMessage();
Jungshik Jange81e1082014-06-05 15:37:59 +0900176
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900177 if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) {
Jungshik Jange81e1082014-06-05 15:37:59 +0900178 return;
179 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900180 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address));
Jinsuk Kim5fba96d2014-07-11 11:51:34 +0900181 addTimer(mState, HdmiConfig.TIMEOUT_MS);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900182 }
183
184 private void startVendorIdStage() {
Jungshik Jang8b308d92014-05-29 21:52:28 +0900185 Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size());
186
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900187 mProcessedDeviceCount = 0;
188 mState = STATE_WAITING_FOR_VENDOR_ID;
189
190 checkAndProceedStage();
191 }
192
193 private void queryVendorId(int address) {
194 if (!verifyValidLogicalAddress(address)) {
195 checkAndProceedStage();
196 return;
197 }
198
199 mActionTimer.clearTimerMessage();
Jungshik Jange81e1082014-06-05 15:37:59 +0900200
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900201 if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) {
Jungshik Jange81e1082014-06-05 15:37:59 +0900202 return;
203 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900204 sendCommand(
205 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address));
Jinsuk Kim5fba96d2014-07-11 11:51:34 +0900206 addTimer(mState, HdmiConfig.TIMEOUT_MS);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900207 }
208
Jungshik Jange81e1082014-06-05 15:37:59 +0900209 private boolean mayProcessMessageIfCached(int address, int opcode) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900210 HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
Jungshik Jange81e1082014-06-05 15:37:59 +0900211 if (message != null) {
212 processCommand(message);
213 return true;
214 }
215 return false;
216 }
217
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900218 @Override
219 boolean processCommand(HdmiCecMessage cmd) {
220 switch (mState) {
221 case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900222 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900223 handleReportPhysicalAddress(cmd);
224 return true;
225 }
226 return false;
227 case STATE_WAITING_FOR_OSD_NAME:
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900228 if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) {
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900229 handleSetOsdName(cmd);
230 return true;
231 }
232 return false;
233 case STATE_WAITING_FOR_VENDOR_ID:
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900234 if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) {
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900235 handleVendorId(cmd);
236 return true;
237 }
238 return false;
239 case STATE_WAITING_FOR_DEVICE_POLLING:
240 // Fall through.
241 default:
242 return false;
243 }
244 }
245
246 private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
247 Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
248
249 DeviceInfo current = mDevices.get(mProcessedDeviceCount);
250 if (current.mLogicalAddress != cmd.getSource()) {
251 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
252 cmd.getSource());
253 return;
254 }
255
256 byte params[] = cmd.getParams();
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900257 current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
Jinsuk Kim2b152012014-07-25 08:22:26 +0900258 current.mPortId = getPortId(current.mPhysicalAddress);
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900259 current.mDeviceType = params[2] & 0xFF;
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900260
Jinsuk Kimbcfa0672014-08-11 11:56:58 +0900261 tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
262 current.mPhysicalAddress);
263
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900264 increaseProcessedDeviceCount();
265 checkAndProceedStage();
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900266 }
267
Jinsuk Kim2b152012014-07-25 08:22:26 +0900268 private int getPortId(int physicalAddress) {
269 return tv().getPortId(physicalAddress);
270 }
271
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900272 private void handleSetOsdName(HdmiCecMessage cmd) {
273 Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
274
275 DeviceInfo current = mDevices.get(mProcessedDeviceCount);
276 if (current.mLogicalAddress != cmd.getSource()) {
277 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
278 cmd.getSource());
279 return;
280 }
281
282 String displayName = null;
283 try {
284 displayName = new String(cmd.getParams(), "US-ASCII");
285 } catch (UnsupportedEncodingException e) {
286 Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
287 // If failed to get display name, use the default name of device.
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900288 displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900289 }
290 current.mDisplayName = displayName;
291 increaseProcessedDeviceCount();
292 checkAndProceedStage();
293 }
294
295 private void handleVendorId(HdmiCecMessage cmd) {
296 Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
297
298 DeviceInfo current = mDevices.get(mProcessedDeviceCount);
299 if (current.mLogicalAddress != cmd.getSource()) {
300 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
301 cmd.getSource());
302 return;
303 }
304
305 byte[] params = cmd.getParams();
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900306 int vendorId = HdmiUtils.threeBytesToInt(params);
307 current.mVendorId = vendorId;
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900308 increaseProcessedDeviceCount();
309 checkAndProceedStage();
310 }
311
312 private void increaseProcessedDeviceCount() {
313 mProcessedDeviceCount++;
Yuncheol Heo46350382014-12-03 22:29:45 +0900314 mTimeoutRetry = 0;
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900315 }
316
317 private void removeDevice(int index) {
318 mDevices.remove(index);
319 }
320
321 private void wrapUpAndFinish() {
Jungshik Jang8b308d92014-05-29 21:52:28 +0900322 Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900323 ArrayList<HdmiDeviceInfo> result = new ArrayList<>();
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900324 for (DeviceInfo info : mDevices) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900325 HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo();
Jungshik Jang8b308d92014-05-29 21:52:28 +0900326 Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900327 result.add(cecDeviceInfo);
328 }
Jungshik Jang8b308d92014-05-29 21:52:28 +0900329 Slog.v(TAG, "--------------------------------------------");
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900330 mCallback.onDeviceDiscoveryDone(result);
331 finish();
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900332 // Process any commands buffered while device discovery action was in progress.
333 tv().processAllDelayedMessages();
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900334 }
335
336 private void checkAndProceedStage() {
337 if (mDevices.isEmpty()) {
338 wrapUpAndFinish();
339 return;
340 }
341
342 // If finished current stage, move on to next stage.
343 if (mProcessedDeviceCount == mDevices.size()) {
344 mProcessedDeviceCount = 0;
345 switch (mState) {
346 case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
347 startOsdNameStage();
348 return;
349 case STATE_WAITING_FOR_OSD_NAME:
350 startVendorIdStage();
351 return;
352 case STATE_WAITING_FOR_VENDOR_ID:
353 wrapUpAndFinish();
354 return;
355 default:
356 return;
357 }
358 } else {
Yuncheol Heo46350382014-12-03 22:29:45 +0900359 sendQueryCommand();
360 }
361 }
362
363 private void sendQueryCommand() {
364 int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
365 switch (mState) {
366 case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
367 queryPhysicalAddress(address);
368 return;
369 case STATE_WAITING_FOR_OSD_NAME:
370 queryOsdName(address);
371 return;
372 case STATE_WAITING_FOR_VENDOR_ID:
373 queryVendorId(address);
374 default:
375 return;
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900376 }
377 }
378
379 @Override
380 void handleTimerEvent(int state) {
381 if (mState == STATE_NONE || mState != state) {
382 return;
383 }
384
Yuncheol Heo46350382014-12-03 22:29:45 +0900385 if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
386 sendQueryCommand();
387 return;
388 }
389 mTimeoutRetry = 0;
Jungshik Jang8b308d92014-05-29 21:52:28 +0900390 Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900391 removeDevice(mProcessedDeviceCount);
392 checkAndProceedStage();
393 }
394}