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