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.util.Slog; 22 23 import com.android.internal.util.Preconditions; 24 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; 25 26 import java.io.UnsupportedEncodingException; 27 import java.util.ArrayList; 28 import java.util.List; 29 30 /** 31 * Feature action that handles device discovery sequences. 32 * Device discovery is launched when device is woken from "Standby" state 33 * or enabled "Control for Hdmi" from disabled state. 34 * 35 * <p>Device discovery goes through the following steps. 36 * <ol> 37 * <li>Poll all non-local devices by sending <Polling Message> 38 * <li>Gather "Physical address" and "device type" of all acknowledged devices 39 * <li>Gather "OSD (display) name" of all acknowledge devices 40 * <li>Gather "Vendor id" of all acknowledge devices 41 * </ol> 42 * We attempt to get OSD name/vendor ID up to 5 times in case the communication fails. 43 */ 44 final class DeviceDiscoveryAction extends HdmiCecFeatureAction { 45 private static final String TAG = "DeviceDiscoveryAction"; 46 47 // State in which the action is waiting for device polling. 48 private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1; 49 // State in which the action is waiting for gathering physical address of non-local devices. 50 private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2; 51 // State in which the action is waiting for gathering osd name of non-local devices. 52 private static final int STATE_WAITING_FOR_OSD_NAME = 3; 53 // State in which the action is waiting for gathering vendor id of non-local devices. 54 private static final int STATE_WAITING_FOR_VENDOR_ID = 4; 55 // State in which the action is waiting for devices to be ready. 56 private static final int STATE_WAITING_FOR_DEVICES = 5; 57 // State in which the action is waiting for gathering power status of non-local devices. 58 private static final int STATE_WAITING_FOR_POWER = 6; 59 60 /** 61 * Interface used to report result of device discovery. 62 */ 63 interface DeviceDiscoveryCallback { 64 /** 65 * Called when device discovery is done. 66 * 67 * @param deviceInfos a list of all non-local devices. It can be empty list. 68 */ onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos)69 void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos); 70 } 71 72 // An internal container used to keep track of device information during 73 // this action. 74 private static final class DeviceInfo { 75 private final int mLogicalAddress; 76 77 private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; 78 private int mPortId = Constants.INVALID_PORT_ID; 79 private int mVendorId = Constants.UNKNOWN_VENDOR_ID; 80 private int mPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; 81 private String mDisplayName = ""; 82 private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE; 83 DeviceInfo(int logicalAddress)84 private DeviceInfo(int logicalAddress) { 85 mLogicalAddress = logicalAddress; 86 } 87 toHdmiDeviceInfo()88 private HdmiDeviceInfo toHdmiDeviceInfo() { 89 return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType, 90 mVendorId, mDisplayName, mPowerStatus); 91 } 92 } 93 94 private final ArrayList<DeviceInfo> mDevices = new ArrayList<>(); 95 private final DeviceDiscoveryCallback mCallback; 96 private int mProcessedDeviceCount = 0; 97 private int mTimeoutRetry = 0; 98 private boolean mIsTvDevice = localDevice().mService.isTvDevice(); 99 private final int mDelayPeriod; 100 101 /** 102 * Constructor. 103 * 104 * @param source an instance of {@link HdmiCecLocalDevice}. 105 * @param delay delay action for this period between query Physical Address and polling 106 */ DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback, int delay)107 DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback, int delay) { 108 super(source); 109 mCallback = Preconditions.checkNotNull(callback); 110 mDelayPeriod = delay; 111 } 112 113 /** 114 * Constructor. 115 * 116 * @param source an instance of {@link HdmiCecLocalDevice}. 117 */ DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback)118 DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) { 119 this(source, callback, 0); 120 } 121 122 @Override start()123 boolean start() { 124 mDevices.clear(); 125 mState = STATE_WAITING_FOR_DEVICE_POLLING; 126 127 pollDevices(new DevicePollingCallback() { 128 @Override 129 public void onPollingFinished(List<Integer> ackedAddress) { 130 if (ackedAddress.isEmpty()) { 131 Slog.v(TAG, "No device is detected."); 132 wrapUpAndFinish(); 133 return; 134 } 135 136 Slog.v(TAG, "Device detected: " + ackedAddress); 137 allocateDevices(ackedAddress); 138 if (mDelayPeriod > 0) { 139 startToDelayAction(); 140 } else { 141 startPhysicalAddressStage(); 142 } 143 } 144 }, Constants.POLL_ITERATION_REVERSE_ORDER 145 | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY); 146 return true; 147 } 148 allocateDevices(List<Integer> addresses)149 private void allocateDevices(List<Integer> addresses) { 150 for (Integer i : addresses) { 151 DeviceInfo info = new DeviceInfo(i); 152 mDevices.add(info); 153 } 154 } 155 startToDelayAction()156 private void startToDelayAction() { 157 Slog.v(TAG, "Waiting for connected devices to be ready"); 158 mState = STATE_WAITING_FOR_DEVICES; 159 160 checkAndProceedStage(); 161 } 162 startPhysicalAddressStage()163 private void startPhysicalAddressStage() { 164 Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size()); 165 mProcessedDeviceCount = 0; 166 mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS; 167 168 checkAndProceedStage(); 169 } 170 verifyValidLogicalAddress(int address)171 private boolean verifyValidLogicalAddress(int address) { 172 return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED; 173 } 174 queryPhysicalAddress(int address)175 private void queryPhysicalAddress(int address) { 176 if (!verifyValidLogicalAddress(address)) { 177 checkAndProceedStage(); 178 return; 179 } 180 181 mActionTimer.clearTimerMessage(); 182 183 // Check cache first and send request if not exist. 184 if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) { 185 return; 186 } 187 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address)); 188 addTimer(mState, HdmiConfig.TIMEOUT_MS); 189 } 190 delayActionWithTimePeriod(int timeDelay)191 private void delayActionWithTimePeriod(int timeDelay) { 192 mActionTimer.clearTimerMessage(); 193 addTimer(mState, timeDelay); 194 } 195 startOsdNameStage()196 private void startOsdNameStage() { 197 Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size()); 198 mProcessedDeviceCount = 0; 199 mState = STATE_WAITING_FOR_OSD_NAME; 200 201 checkAndProceedStage(); 202 } 203 queryOsdName(int address)204 private void queryOsdName(int address) { 205 if (!verifyValidLogicalAddress(address)) { 206 checkAndProceedStage(); 207 return; 208 } 209 210 mActionTimer.clearTimerMessage(); 211 212 if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) { 213 return; 214 } 215 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address)); 216 addTimer(mState, HdmiConfig.TIMEOUT_MS); 217 } 218 startVendorIdStage()219 private void startVendorIdStage() { 220 Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size()); 221 222 mProcessedDeviceCount = 0; 223 mState = STATE_WAITING_FOR_VENDOR_ID; 224 225 checkAndProceedStage(); 226 } 227 queryVendorId(int address)228 private void queryVendorId(int address) { 229 if (!verifyValidLogicalAddress(address)) { 230 checkAndProceedStage(); 231 return; 232 } 233 234 mActionTimer.clearTimerMessage(); 235 236 if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) { 237 return; 238 } 239 sendCommand( 240 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address)); 241 addTimer(mState, HdmiConfig.TIMEOUT_MS); 242 } 243 startPowerStatusStage()244 private void startPowerStatusStage() { 245 Slog.v(TAG, "Start [Power Status Stage]:" + mDevices.size()); 246 mProcessedDeviceCount = 0; 247 mState = STATE_WAITING_FOR_POWER; 248 249 checkAndProceedStage(); 250 } 251 queryPowerStatus(int address)252 private void queryPowerStatus(int address) { 253 if (!verifyValidLogicalAddress(address)) { 254 checkAndProceedStage(); 255 return; 256 } 257 258 mActionTimer.clearTimerMessage(); 259 260 if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_POWER_STATUS)) { 261 return; 262 } 263 sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address)); 264 addTimer(mState, HdmiConfig.TIMEOUT_MS); 265 } 266 mayProcessMessageIfCached(int address, int opcode)267 private boolean mayProcessMessageIfCached(int address, int opcode) { 268 HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode); 269 if (message != null) { 270 processCommand(message); 271 return true; 272 } 273 return false; 274 } 275 276 @Override processCommand(HdmiCecMessage cmd)277 boolean processCommand(HdmiCecMessage cmd) { 278 switch (mState) { 279 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 280 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) { 281 handleReportPhysicalAddress(cmd); 282 return true; 283 } 284 return false; 285 case STATE_WAITING_FOR_OSD_NAME: 286 if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) { 287 handleSetOsdName(cmd); 288 return true; 289 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) && 290 ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_OSD_NAME)) { 291 handleSetOsdName(cmd); 292 return true; 293 } 294 return false; 295 case STATE_WAITING_FOR_VENDOR_ID: 296 if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) { 297 handleVendorId(cmd); 298 return true; 299 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) && 300 ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID)) { 301 handleVendorId(cmd); 302 return true; 303 } 304 return false; 305 case STATE_WAITING_FOR_POWER: 306 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) { 307 handleReportPowerStatus(cmd); 308 return true; 309 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) 310 && ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_REPORT_POWER_STATUS)) { 311 handleReportPowerStatus(cmd); 312 return true; 313 } 314 return false; 315 case STATE_WAITING_FOR_DEVICE_POLLING: 316 // Fall through. 317 default: 318 return false; 319 } 320 } 321 handleReportPhysicalAddress(HdmiCecMessage cmd)322 private void handleReportPhysicalAddress(HdmiCecMessage cmd) { 323 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 324 325 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 326 if (current.mLogicalAddress != cmd.getSource()) { 327 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 328 cmd.getSource()); 329 return; 330 } 331 332 byte params[] = cmd.getParams(); 333 current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params); 334 current.mPortId = getPortId(current.mPhysicalAddress); 335 current.mDeviceType = params[2] & 0xFF; 336 current.mDisplayName = HdmiUtils.getDefaultDeviceName(current.mDeviceType); 337 338 // This is to manager CEC device separately in case they don't have address. 339 if (mIsTvDevice) { 340 tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType, 341 current.mPhysicalAddress); 342 } 343 increaseProcessedDeviceCount(); 344 checkAndProceedStage(); 345 } 346 347 private int getPortId(int physicalAddress) { 348 return mIsTvDevice ? tv().getPortId(physicalAddress) 349 : source().getPortId(physicalAddress); 350 } 351 352 private void handleSetOsdName(HdmiCecMessage cmd) { 353 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 354 355 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 356 if (current.mLogicalAddress != cmd.getSource()) { 357 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 358 cmd.getSource()); 359 return; 360 } 361 362 String displayName = null; 363 try { 364 if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) { 365 displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress); 366 } else { 367 displayName = new String(cmd.getParams(), "US-ASCII"); 368 } 369 } catch (UnsupportedEncodingException e) { 370 Slog.w(TAG, "Failed to decode display name: " + cmd.toString()); 371 // If failed to get display name, use the default name of device. 372 displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress); 373 } 374 current.mDisplayName = displayName; 375 increaseProcessedDeviceCount(); 376 checkAndProceedStage(); 377 } 378 379 private void handleVendorId(HdmiCecMessage cmd) { 380 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 381 382 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 383 if (current.mLogicalAddress != cmd.getSource()) { 384 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 385 cmd.getSource()); 386 return; 387 } 388 389 if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) { 390 byte[] params = cmd.getParams(); 391 int vendorId = HdmiUtils.threeBytesToInt(params); 392 current.mVendorId = vendorId; 393 } 394 395 increaseProcessedDeviceCount(); 396 checkAndProceedStage(); 397 } 398 399 private void handleReportPowerStatus(HdmiCecMessage cmd) { 400 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 401 402 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 403 if (current.mLogicalAddress != cmd.getSource()) { 404 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" 405 + cmd.getSource()); 406 return; 407 } 408 409 if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) { 410 byte[] params = cmd.getParams(); 411 int powerStatus = params[0] & 0xFF; 412 current.mPowerStatus = powerStatus; 413 } 414 415 increaseProcessedDeviceCount(); 416 checkAndProceedStage(); 417 } 418 419 private void increaseProcessedDeviceCount() { 420 mProcessedDeviceCount++; 421 mTimeoutRetry = 0; 422 } 423 424 private void removeDevice(int index) { 425 mDevices.remove(index); 426 } 427 428 private void wrapUpAndFinish() { 429 Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------"); 430 ArrayList<HdmiDeviceInfo> result = new ArrayList<>(); 431 for (DeviceInfo info : mDevices) { 432 HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo(); 433 Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo); 434 result.add(cecDeviceInfo); 435 } 436 Slog.v(TAG, "--------------------------------------------"); 437 mCallback.onDeviceDiscoveryDone(result); 438 finish(); 439 // Process any commands buffered while device discovery action was in progress. 440 if (mIsTvDevice) { 441 tv().processAllDelayedMessages(); 442 } 443 } 444 445 private void checkAndProceedStage() { 446 if (mDevices.isEmpty()) { 447 wrapUpAndFinish(); 448 return; 449 } 450 451 // If finished current stage, move on to next stage. 452 if (mProcessedDeviceCount == mDevices.size()) { 453 mProcessedDeviceCount = 0; 454 switch (mState) { 455 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 456 startOsdNameStage(); 457 return; 458 case STATE_WAITING_FOR_OSD_NAME: 459 startVendorIdStage(); 460 return; 461 case STATE_WAITING_FOR_VENDOR_ID: 462 startPowerStatusStage(); 463 return; 464 case STATE_WAITING_FOR_POWER: 465 wrapUpAndFinish(); 466 return; 467 default: 468 return; 469 } 470 } else { 471 sendQueryCommand(); 472 } 473 } 474 475 private void sendQueryCommand() { 476 int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress; 477 switch (mState) { 478 case STATE_WAITING_FOR_DEVICES: 479 delayActionWithTimePeriod(mDelayPeriod); 480 return; 481 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 482 queryPhysicalAddress(address); 483 return; 484 case STATE_WAITING_FOR_OSD_NAME: 485 queryOsdName(address); 486 return; 487 case STATE_WAITING_FOR_VENDOR_ID: 488 queryVendorId(address); 489 return; 490 case STATE_WAITING_FOR_POWER: 491 queryPowerStatus(address); 492 return; 493 default: 494 return; 495 } 496 } 497 498 @Override 499 void handleTimerEvent(int state) { 500 if (mState == STATE_NONE || mState != state) { 501 return; 502 } 503 504 if (mState == STATE_WAITING_FOR_DEVICES) { 505 startPhysicalAddressStage(); 506 return; 507 } 508 if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) { 509 sendQueryCommand(); 510 return; 511 } 512 mTimeoutRetry = 0; 513 Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount); 514 if (mState != STATE_WAITING_FOR_POWER && mState != STATE_WAITING_FOR_OSD_NAME) { 515 // We don't need to remove the device info if the power status is unknown. 516 // Some device does not have preferred OSD name and does not respond to Give OSD name. 517 // Like LG TV. We can give it default device name and not remove it. 518 removeDevice(mProcessedDeviceCount); 519 } else { 520 increaseProcessedDeviceCount(); 521 } 522 checkAndProceedStage(); 523 } 524 } 525