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 // Check if the action was finished before the callback was called. 144 // See {@link HdmiCecFeatureAction#finish}. 145 if (mState == STATE_NONE) { 146 Slog.v(TAG, "Action was already finished before the callback was called."); 147 wrapUpAndFinish(); 148 return; 149 } 150 Slog.v(TAG, "Device detected: " + ackedAddress); 151 allocateDevices(ackedAddress); 152 if (mDelayPeriod > 0) { 153 startToDelayAction(); 154 } else { 155 startPhysicalAddressStage(); 156 } 157 } 158 }, Constants.POLL_ITERATION_REVERSE_ORDER 159 | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY); 160 return true; 161 } 162 allocateDevices(List<Integer> addresses)163 private void allocateDevices(List<Integer> addresses) { 164 for (Integer i : addresses) { 165 DeviceInfo info = new DeviceInfo(i); 166 mDevices.add(info); 167 } 168 } 169 startToDelayAction()170 private void startToDelayAction() { 171 Slog.v(TAG, "Waiting for connected devices to be ready"); 172 mState = STATE_WAITING_FOR_DEVICES; 173 174 checkAndProceedStage(); 175 } 176 startPhysicalAddressStage()177 private void startPhysicalAddressStage() { 178 Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size()); 179 mProcessedDeviceCount = 0; 180 mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS; 181 182 checkAndProceedStage(); 183 } 184 verifyValidLogicalAddress(int address)185 private boolean verifyValidLogicalAddress(int address) { 186 return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED; 187 } 188 queryPhysicalAddress(int address)189 private void queryPhysicalAddress(int address) { 190 if (!verifyValidLogicalAddress(address)) { 191 checkAndProceedStage(); 192 return; 193 } 194 195 mActionTimer.clearTimerMessage(); 196 197 // Check cache first and send request if not exist. 198 if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) { 199 return; 200 } 201 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address)); 202 addTimer(mState, HdmiConfig.TIMEOUT_MS); 203 } 204 delayActionWithTimePeriod(int timeDelay)205 private void delayActionWithTimePeriod(int timeDelay) { 206 mActionTimer.clearTimerMessage(); 207 addTimer(mState, timeDelay); 208 } 209 startOsdNameStage()210 private void startOsdNameStage() { 211 Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size()); 212 mProcessedDeviceCount = 0; 213 mState = STATE_WAITING_FOR_OSD_NAME; 214 215 checkAndProceedStage(); 216 } 217 queryOsdName(int address)218 private void queryOsdName(int address) { 219 if (!verifyValidLogicalAddress(address)) { 220 checkAndProceedStage(); 221 return; 222 } 223 224 mActionTimer.clearTimerMessage(); 225 226 if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) { 227 return; 228 } 229 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address)); 230 addTimer(mState, HdmiConfig.TIMEOUT_MS); 231 } 232 startVendorIdStage()233 private void startVendorIdStage() { 234 Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size()); 235 236 mProcessedDeviceCount = 0; 237 mState = STATE_WAITING_FOR_VENDOR_ID; 238 239 checkAndProceedStage(); 240 } 241 queryVendorId(int address)242 private void queryVendorId(int address) { 243 if (!verifyValidLogicalAddress(address)) { 244 checkAndProceedStage(); 245 return; 246 } 247 248 mActionTimer.clearTimerMessage(); 249 250 if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) { 251 return; 252 } 253 sendCommand( 254 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address)); 255 addTimer(mState, HdmiConfig.TIMEOUT_MS); 256 } 257 startPowerStatusStage()258 private void startPowerStatusStage() { 259 Slog.v(TAG, "Start [Power Status Stage]:" + mDevices.size()); 260 mProcessedDeviceCount = 0; 261 mState = STATE_WAITING_FOR_POWER; 262 263 checkAndProceedStage(); 264 } 265 queryPowerStatus(int address)266 private void queryPowerStatus(int address) { 267 if (!verifyValidLogicalAddress(address)) { 268 checkAndProceedStage(); 269 return; 270 } 271 272 mActionTimer.clearTimerMessage(); 273 274 if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_POWER_STATUS)) { 275 return; 276 } 277 sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address)); 278 addTimer(mState, HdmiConfig.TIMEOUT_MS); 279 } 280 mayProcessMessageIfCached(int address, int opcode)281 private boolean mayProcessMessageIfCached(int address, int opcode) { 282 HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode); 283 if (message != null) { 284 processCommand(message); 285 return true; 286 } 287 return false; 288 } 289 290 @Override processCommand(HdmiCecMessage cmd)291 boolean processCommand(HdmiCecMessage cmd) { 292 switch (mState) { 293 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 294 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) { 295 handleReportPhysicalAddress(cmd); 296 return true; 297 } 298 return false; 299 case STATE_WAITING_FOR_OSD_NAME: 300 if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) { 301 handleSetOsdName(cmd); 302 return true; 303 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) && 304 ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_OSD_NAME)) { 305 handleSetOsdName(cmd); 306 return true; 307 } 308 return false; 309 case STATE_WAITING_FOR_VENDOR_ID: 310 if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) { 311 handleVendorId(cmd); 312 return true; 313 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) && 314 ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID)) { 315 handleVendorId(cmd); 316 return true; 317 } 318 return false; 319 case STATE_WAITING_FOR_POWER: 320 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) { 321 handleReportPowerStatus(cmd); 322 return true; 323 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) 324 && ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_REPORT_POWER_STATUS)) { 325 handleReportPowerStatus(cmd); 326 return true; 327 } 328 return false; 329 case STATE_WAITING_FOR_DEVICE_POLLING: 330 // Fall through. 331 default: 332 return false; 333 } 334 } 335 handleReportPhysicalAddress(HdmiCecMessage cmd)336 private void handleReportPhysicalAddress(HdmiCecMessage cmd) { 337 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 338 339 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 340 if (current.mLogicalAddress != cmd.getSource()) { 341 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 342 cmd.getSource()); 343 return; 344 } 345 346 byte params[] = cmd.getParams(); 347 current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params); 348 current.mPortId = getPortId(current.mPhysicalAddress); 349 current.mDeviceType = params[2] & 0xFF; 350 // Keep display name empty. TIF fallbacks to the service label provided by the package mg. 351 current.mDisplayName = ""; 352 353 // This is to manager CEC device separately in case they don't have address. 354 if (mIsTvDevice) { 355 localDevice().mService.getHdmiCecNetwork().updateCecSwitchInfo(current.mLogicalAddress, 356 current.mDeviceType, 357 current.mPhysicalAddress); 358 } 359 increaseProcessedDeviceCount(); 360 checkAndProceedStage(); 361 } 362 363 private int getPortId(int physicalAddress) { 364 return mIsTvDevice ? tv().getPortId(physicalAddress) 365 : source().getPortId(physicalAddress); 366 } 367 368 private void handleSetOsdName(HdmiCecMessage cmd) { 369 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 370 371 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 372 if (current.mLogicalAddress != cmd.getSource()) { 373 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 374 cmd.getSource()); 375 return; 376 } 377 378 String displayName = ""; 379 try { 380 if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) { 381 displayName = new String(cmd.getParams(), "US-ASCII"); 382 } 383 } catch (UnsupportedEncodingException e) { 384 Slog.w(TAG, "Failed to decode display name: " + cmd.toString()); 385 } 386 current.mDisplayName = displayName; 387 increaseProcessedDeviceCount(); 388 checkAndProceedStage(); 389 } 390 391 private void handleVendorId(HdmiCecMessage cmd) { 392 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 393 394 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 395 if (current.mLogicalAddress != cmd.getSource()) { 396 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 397 cmd.getSource()); 398 return; 399 } 400 401 if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) { 402 byte[] params = cmd.getParams(); 403 int vendorId = HdmiUtils.threeBytesToInt(params); 404 current.mVendorId = vendorId; 405 } 406 407 increaseProcessedDeviceCount(); 408 checkAndProceedStage(); 409 } 410 411 private void handleReportPowerStatus(HdmiCecMessage cmd) { 412 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 413 414 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 415 if (current.mLogicalAddress != cmd.getSource()) { 416 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" 417 + cmd.getSource()); 418 return; 419 } 420 421 if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) { 422 byte[] params = cmd.getParams(); 423 int powerStatus = params[0] & 0xFF; 424 current.mPowerStatus = powerStatus; 425 } 426 427 increaseProcessedDeviceCount(); 428 checkAndProceedStage(); 429 } 430 431 private void increaseProcessedDeviceCount() { 432 mProcessedDeviceCount++; 433 mTimeoutRetry = 0; 434 } 435 436 private void removeDevice(int index) { 437 mDevices.remove(index); 438 } 439 440 private void wrapUpAndFinish() { 441 Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------"); 442 ArrayList<HdmiDeviceInfo> result = new ArrayList<>(); 443 for (DeviceInfo info : mDevices) { 444 HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo(); 445 Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo); 446 result.add(cecDeviceInfo); 447 } 448 Slog.v(TAG, "--------------------------------------------"); 449 mCallback.onDeviceDiscoveryDone(result); 450 finish(); 451 // Process any commands buffered while device discovery action was in progress. 452 if (mIsTvDevice) { 453 tv().processAllDelayedMessages(); 454 } 455 } 456 457 private void checkAndProceedStage() { 458 if (mDevices.isEmpty()) { 459 wrapUpAndFinish(); 460 return; 461 } 462 // If finished current stage, move on to next stage. 463 if (mProcessedDeviceCount == mDevices.size()) { 464 mProcessedDeviceCount = 0; 465 switch (mState) { 466 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 467 startOsdNameStage(); 468 return; 469 case STATE_WAITING_FOR_OSD_NAME: 470 startVendorIdStage(); 471 return; 472 case STATE_WAITING_FOR_VENDOR_ID: 473 startPowerStatusStage(); 474 return; 475 case STATE_WAITING_FOR_POWER: 476 wrapUpAndFinish(); 477 return; 478 default: 479 return; 480 } 481 } else { 482 sendQueryCommand(); 483 } 484 } 485 486 private void sendQueryCommand() { 487 int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress; 488 switch (mState) { 489 case STATE_WAITING_FOR_DEVICES: 490 delayActionWithTimePeriod(mDelayPeriod); 491 return; 492 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 493 queryPhysicalAddress(address); 494 return; 495 case STATE_WAITING_FOR_OSD_NAME: 496 queryOsdName(address); 497 return; 498 case STATE_WAITING_FOR_VENDOR_ID: 499 queryVendorId(address); 500 return; 501 case STATE_WAITING_FOR_POWER: 502 queryPowerStatus(address); 503 return; 504 default: 505 return; 506 } 507 } 508 509 @Override 510 void handleTimerEvent(int state) { 511 if (mState == STATE_NONE || mState != state) { 512 return; 513 } 514 515 if (mState == STATE_WAITING_FOR_DEVICES) { 516 startPhysicalAddressStage(); 517 return; 518 } 519 if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) { 520 sendQueryCommand(); 521 return; 522 } 523 mTimeoutRetry = 0; 524 Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount); 525 if (mState != STATE_WAITING_FOR_POWER && mState != STATE_WAITING_FOR_OSD_NAME) { 526 // We don't need to remove the device info if the power status is unknown. 527 // Some device does not have preferred OSD name and does not respond to Give OSD name. 528 // Like LG TV. We can give it default device name and not remove it. 529 removeDevice(mProcessedDeviceCount); 530 } else { 531 increaseProcessedDeviceCount(); 532 } 533 checkAndProceedStage(); 534 } 535 } 536