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 */ 42 final class DeviceDiscoveryAction extends HdmiCecFeatureAction { 43 private static final String TAG = "DeviceDiscoveryAction"; 44 45 // State in which the action is waiting for device polling. 46 private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1; 47 // State in which the action is waiting for gathering physical address of non-local devices. 48 private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2; 49 // State in which the action is waiting for gathering osd name of non-local devices. 50 private static final int STATE_WAITING_FOR_OSD_NAME = 3; 51 // State in which the action is waiting for gathering vendor id of non-local devices. 52 private static final int STATE_WAITING_FOR_VENDOR_ID = 4; 53 54 /** 55 * Interface used to report result of device discovery. 56 */ 57 interface DeviceDiscoveryCallback { 58 /** 59 * Called when device discovery is done. 60 * 61 * @param deviceInfos a list of all non-local devices. It can be empty list. 62 */ 63 void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos); 64 } 65 66 // An internal container used to keep track of device information during 67 // this action. 68 private static final class DeviceInfo { 69 private final int mLogicalAddress; 70 71 private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; 72 private int mPortId = Constants.INVALID_PORT_ID; 73 private int mVendorId = Constants.UNKNOWN_VENDOR_ID; 74 private String mDisplayName = ""; 75 private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE; 76 77 private DeviceInfo(int logicalAddress) { 78 mLogicalAddress = logicalAddress; 79 } 80 81 private HdmiDeviceInfo toHdmiDeviceInfo() { 82 return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType, 83 mVendorId, mDisplayName); 84 } 85 } 86 87 private final ArrayList<DeviceInfo> mDevices = new ArrayList<>(); 88 private final DeviceDiscoveryCallback mCallback; 89 private int mProcessedDeviceCount = 0; 90 91 /** 92 * Constructor. 93 * 94 * @param source an instance of {@link HdmiCecLocalDevice}. 95 */ 96 DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) { 97 super(source); 98 mCallback = Preconditions.checkNotNull(callback); 99 } 100 101 @Override 102 boolean start() { 103 mDevices.clear(); 104 mState = STATE_WAITING_FOR_DEVICE_POLLING; 105 106 pollDevices(new DevicePollingCallback() { 107 @Override 108 public void onPollingFinished(List<Integer> ackedAddress) { 109 if (ackedAddress.isEmpty()) { 110 Slog.v(TAG, "No device is detected."); 111 wrapUpAndFinish(); 112 return; 113 } 114 115 Slog.v(TAG, "Device detected: " + ackedAddress); 116 allocateDevices(ackedAddress); 117 startPhysicalAddressStage(); 118 } 119 }, Constants.POLL_ITERATION_REVERSE_ORDER 120 | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY); 121 return true; 122 } 123 124 private void allocateDevices(List<Integer> addresses) { 125 for (Integer i : addresses) { 126 DeviceInfo info = new DeviceInfo(i); 127 mDevices.add(info); 128 } 129 } 130 131 private void startPhysicalAddressStage() { 132 Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size()); 133 mProcessedDeviceCount = 0; 134 mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS; 135 136 checkAndProceedStage(); 137 } 138 139 private boolean verifyValidLogicalAddress(int address) { 140 return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED; 141 } 142 143 private void queryPhysicalAddress(int address) { 144 if (!verifyValidLogicalAddress(address)) { 145 checkAndProceedStage(); 146 return; 147 } 148 149 mActionTimer.clearTimerMessage(); 150 151 // Check cache first and send request if not exist. 152 if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) { 153 return; 154 } 155 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address)); 156 addTimer(mState, HdmiConfig.TIMEOUT_MS); 157 } 158 159 private void startOsdNameStage() { 160 Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size()); 161 mProcessedDeviceCount = 0; 162 mState = STATE_WAITING_FOR_OSD_NAME; 163 164 checkAndProceedStage(); 165 } 166 167 private void queryOsdName(int address) { 168 if (!verifyValidLogicalAddress(address)) { 169 checkAndProceedStage(); 170 return; 171 } 172 173 mActionTimer.clearTimerMessage(); 174 175 if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) { 176 return; 177 } 178 sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address)); 179 addTimer(mState, HdmiConfig.TIMEOUT_MS); 180 } 181 182 private void startVendorIdStage() { 183 Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size()); 184 185 mProcessedDeviceCount = 0; 186 mState = STATE_WAITING_FOR_VENDOR_ID; 187 188 checkAndProceedStage(); 189 } 190 191 private void queryVendorId(int address) { 192 if (!verifyValidLogicalAddress(address)) { 193 checkAndProceedStage(); 194 return; 195 } 196 197 mActionTimer.clearTimerMessage(); 198 199 if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) { 200 return; 201 } 202 sendCommand( 203 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address)); 204 addTimer(mState, HdmiConfig.TIMEOUT_MS); 205 } 206 207 private boolean mayProcessMessageIfCached(int address, int opcode) { 208 HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode); 209 if (message != null) { 210 processCommand(message); 211 return true; 212 } 213 return false; 214 } 215 216 @Override 217 boolean processCommand(HdmiCecMessage cmd) { 218 switch (mState) { 219 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 220 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) { 221 handleReportPhysicalAddress(cmd); 222 return true; 223 } 224 return false; 225 case STATE_WAITING_FOR_OSD_NAME: 226 if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) { 227 handleSetOsdName(cmd); 228 return true; 229 } 230 return false; 231 case STATE_WAITING_FOR_VENDOR_ID: 232 if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) { 233 handleVendorId(cmd); 234 return true; 235 } 236 return false; 237 case STATE_WAITING_FOR_DEVICE_POLLING: 238 // Fall through. 239 default: 240 return false; 241 } 242 } 243 244 private void handleReportPhysicalAddress(HdmiCecMessage cmd) { 245 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 246 247 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 248 if (current.mLogicalAddress != cmd.getSource()) { 249 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 250 cmd.getSource()); 251 return; 252 } 253 254 byte params[] = cmd.getParams(); 255 current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params); 256 current.mPortId = getPortId(current.mPhysicalAddress); 257 current.mDeviceType = params[2] & 0xFF; 258 259 tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType, 260 current.mPhysicalAddress); 261 262 increaseProcessedDeviceCount(); 263 checkAndProceedStage(); 264 } 265 266 private int getPortId(int physicalAddress) { 267 return tv().getPortId(physicalAddress); 268 } 269 270 private void handleSetOsdName(HdmiCecMessage cmd) { 271 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 272 273 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 274 if (current.mLogicalAddress != cmd.getSource()) { 275 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 276 cmd.getSource()); 277 return; 278 } 279 280 String displayName = null; 281 try { 282 displayName = new String(cmd.getParams(), "US-ASCII"); 283 } catch (UnsupportedEncodingException e) { 284 Slog.w(TAG, "Failed to decode display name: " + cmd.toString()); 285 // If failed to get display name, use the default name of device. 286 displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress); 287 } 288 current.mDisplayName = displayName; 289 increaseProcessedDeviceCount(); 290 checkAndProceedStage(); 291 } 292 293 private void handleVendorId(HdmiCecMessage cmd) { 294 Preconditions.checkState(mProcessedDeviceCount < mDevices.size()); 295 296 DeviceInfo current = mDevices.get(mProcessedDeviceCount); 297 if (current.mLogicalAddress != cmd.getSource()) { 298 Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" + 299 cmd.getSource()); 300 return; 301 } 302 303 byte[] params = cmd.getParams(); 304 int vendorId = HdmiUtils.threeBytesToInt(params); 305 current.mVendorId = vendorId; 306 increaseProcessedDeviceCount(); 307 checkAndProceedStage(); 308 } 309 310 private void increaseProcessedDeviceCount() { 311 mProcessedDeviceCount++; 312 } 313 314 private void removeDevice(int index) { 315 mDevices.remove(index); 316 } 317 318 private void wrapUpAndFinish() { 319 Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------"); 320 ArrayList<HdmiDeviceInfo> result = new ArrayList<>(); 321 for (DeviceInfo info : mDevices) { 322 HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo(); 323 Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo); 324 result.add(cecDeviceInfo); 325 } 326 Slog.v(TAG, "--------------------------------------------"); 327 mCallback.onDeviceDiscoveryDone(result); 328 finish(); 329 } 330 331 private void checkAndProceedStage() { 332 if (mDevices.isEmpty()) { 333 wrapUpAndFinish(); 334 return; 335 } 336 337 // If finished current stage, move on to next stage. 338 if (mProcessedDeviceCount == mDevices.size()) { 339 mProcessedDeviceCount = 0; 340 switch (mState) { 341 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 342 startOsdNameStage(); 343 return; 344 case STATE_WAITING_FOR_OSD_NAME: 345 startVendorIdStage(); 346 return; 347 case STATE_WAITING_FOR_VENDOR_ID: 348 wrapUpAndFinish(); 349 return; 350 default: 351 return; 352 } 353 } else { 354 int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress; 355 switch (mState) { 356 case STATE_WAITING_FOR_PHYSICAL_ADDRESS: 357 queryPhysicalAddress(address); 358 return; 359 case STATE_WAITING_FOR_OSD_NAME: 360 queryOsdName(address); 361 return; 362 case STATE_WAITING_FOR_VENDOR_ID: 363 queryVendorId(address); 364 default: 365 return; 366 } 367 } 368 } 369 370 @Override 371 void handleTimerEvent(int state) { 372 if (mState == STATE_NONE || mState != state) { 373 return; 374 } 375 376 Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount); 377 removeDevice(mProcessedDeviceCount); 378 checkAndProceedStage(); 379 } 380 } 381