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.HdmiDeviceInfo; 20 import android.util.Slog; 21 22 import com.android.internal.util.Preconditions; 23 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; 24 25 import java.io.UnsupportedEncodingException; 26 import java.util.ArrayList; 27 import 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 <Polling Message> 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> 41 * We attempt to get OSD name/vendor ID up to 5 times in case the communication fails. 42 */ 43 final class DeviceDiscoveryAction extends HdmiCecFeatureAction { 44 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 55 /** 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 */ 64 void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos); 65 } 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 72 private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; 73 private int mPortId = Constants.INVALID_PORT_ID; 74 private int mVendorId = Constants.UNKNOWN_VENDOR_ID; 75 private String mDisplayName = ""; 76 private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE; 77 78 private DeviceInfo(int logicalAddress) { 79 mLogicalAddress = logicalAddress; 80 } 81 82 private HdmiDeviceInfo toHdmiDeviceInfo() { 83 return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType, 84 mVendorId, mDisplayName); 85 } 86 } 87 88 private final ArrayList<DeviceInfo> mDevices = new ArrayList<>(); 89 private final DeviceDiscoveryCallback mCallback; 90 private int mProcessedDeviceCount = 0; 91 private int mTimeoutRetry = 0; 92 93 /** 94 * Constructor. 95 * 96 * @param source an instance of {@link HdmiCecLocalDevice}. 97 */ 98 DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) { 99 super(source); 100 mCallback = Preconditions.checkNotNull(callback); 101 } 102 103 @Override 104 boolean start() { 105 mDevices.clear(); 106 mState = STATE_WAITING_FOR_DEVICE_POLLING; 107 108 pollDevices(new DevicePollingCallback() { 109 @Override 110 public void onPollingFinished(List<Integer> ackedAddress) { 111 if (ackedAddress.isEmpty()) { 112 Slog.v(TAG, "No device is detected."); 113 wrapUpAndFinish(); 114 return; 115 } 116 117 Slog.v(TAG, "Device detected: " + ackedAddress); 118 allocateDevices(ackedAddress); 119 startPhysicalAddressStage(); 120 } 121 }, Constants.POLL_ITERATION_REVERSE_ORDER 122 | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY); 123 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() { 134 Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size()); 135 mProcessedDeviceCount = 0; 136 mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS; 137 138 checkAndProceedStage(); 139 } 140 141 private boolean verifyValidLogicalAddress(int address) { 142 return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED; 143 } 144 145 private void queryPhysicalAddress(int address) { 146 if (!verifyValidLogicalAddress(address)) { 147 checkAndProceedStage(); 148 return; 149 } 150 151 mActionTimer.clearTimerMessage(); 152 153 // Check cache first and send request if not exist. 154 if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) { 155 return; 156 } 157 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address)); 158 addTimer(mState, HdmiConfig.TIMEOUT_MS); 159 } 160 161 private void startOsdNameStage() { 162 Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size()); 163 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(); 176 177 if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) { 178 return; 179 } 180 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address)); 181 addTimer(mState, HdmiConfig.TIMEOUT_MS); 182 } 183 184 private void startVendorIdStage() { 185 Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size()); 186 187 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(); 200 201 if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) { 202 return; 203 } 204 sendCommand( 205 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address)); 206 addTimer(mState, HdmiConfig.TIMEOUT_MS); 207 } 208 209 private boolean mayProcessMessageIfCached(int address, int opcode) { 210 HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode); 211 if (message != null) { 212 processCommand(message); 213 return true; 214 } 215 return false; 216 } 217 218 @Override 219 boolean processCommand(HdmiCecMessage cmd) { 220 switch (mState) { 221 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 222 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) { 223 handleReportPhysicalAddress(cmd); 224 return true; 225 } 226 return false; 227 case STATE_WAITING_FOR_OSD_NAME: 228 if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) { 229 handleSetOsdName(cmd); 230 return true; 231 } 232 return false; 233 case STATE_WAITING_FOR_VENDOR_ID: 234 if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) { 235 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(); 257 current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params); 258 current.mPortId = getPortId(current.mPhysicalAddress); 259 current.mDeviceType = params[2] & 0xFF; 260 261 tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType, 262 current.mPhysicalAddress); 263 264 increaseProcessedDeviceCount(); 265 checkAndProceedStage(); 266 } 267 268 private int getPortId(int physicalAddress) { 269 return tv().getPortId(physicalAddress); 270 } 271 272 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. 288 displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress); 289 } 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(); 306 int vendorId = HdmiUtils.threeBytesToInt(params); 307 current.mVendorId = vendorId; 308 increaseProcessedDeviceCount(); 309 checkAndProceedStage(); 310 } 311 312 private void increaseProcessedDeviceCount() { 313 mProcessedDeviceCount++; 314 mTimeoutRetry = 0; 315 } 316 317 private void removeDevice(int index) { 318 mDevices.remove(index); 319 } 320 321 private void wrapUpAndFinish() { 322 Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------"); 323 ArrayList<HdmiDeviceInfo> result = new ArrayList<>(); 324 for (DeviceInfo info : mDevices) { 325 HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo(); 326 Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo); 327 result.add(cecDeviceInfo); 328 } 329 Slog.v(TAG, "--------------------------------------------"); 330 mCallback.onDeviceDiscoveryDone(result); 331 finish(); 332 // Process any commands buffered while device discovery action was in progress. 333 tv().processAllDelayedMessages(); 334 } 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 { 359 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; 376 } 377 } 378 379 @Override 380 void handleTimerEvent(int state) { 381 if (mState == STATE_NONE || mState != state) { 382 return; 383 } 384 385 if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) { 386 sendQueryCommand(); 387 return; 388 } 389 mTimeoutRetry = 0; 390 Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount); 391 removeDevice(mProcessedDeviceCount); 392 checkAndProceedStage(); 393 } 394 } 395